From b7ba7521af2cb48cce4a60266c1ec4e6130cc4d2 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 10 Oct 2023 10:47:42 -0300 Subject: [PATCH 01/15] regression: test license from cloud (#30609) Co-authored-by: Diego Sampaio --- .../app/cloud/server/functions/getWorkspaceLicense.ts | 2 +- .../server/functions/syncWorkspace/announcementSync.ts | 2 +- .../app/cloud/server/functions/syncWorkspace/index.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts index 2ab8a4b27a62..f9f0cfadc669 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts @@ -73,7 +73,7 @@ export async function getWorkspaceLicense(): Promise<{ updated: boolean; license const payload = await fetchCloudWorkspaceLicensePayload({ token }); - if (Date.parse(payload.updatedAt) <= currentLicense._updatedAt.getTime()) { + if (currentLicense.value && Date.parse(payload.updatedAt) <= currentLicense._updatedAt.getTime()) { return fromCurrentLicense(); } diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts index 26d98b4a7574..f3885c1e95c2 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/announcementSync.ts @@ -107,7 +107,7 @@ export async function announcementSync() { } catch (err) { SystemLogger.error({ msg: 'Failed to sync with Rocket.Chat Cloud', - url: '/sync', + url: '/comms/workspace', err, }); } diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts index 3173e652afe5..bdd898b510f7 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts @@ -1,3 +1,4 @@ +import { SystemLogger } from '../../../../../server/lib/logger/system'; import { CloudWorkspaceAccessTokenError } from '../getWorkspaceAccessToken'; import { getCachedSupportedVersionsToken } from '../supportedVersionsToken/supportedVersionsToken'; import { announcementSync } from './announcementSync'; @@ -7,10 +8,11 @@ export async function syncWorkspace() { try { await syncCloudData(); await announcementSync(); - } catch (error) { - if (error instanceof CloudWorkspaceAccessTokenError) { + } catch (err) { + if (err instanceof CloudWorkspaceAccessTokenError) { // TODO: Remove License if there is no access token } + SystemLogger.error({ msg: 'Error during workspace sync', err }); } await getCachedSupportedVersionsToken.reset(); From 058650128d4b8c630b0767268073775263ddef9e Mon Sep 17 00:00:00 2001 From: csuadev <72958726+csuadev@users.noreply.github.com> Date: Tue, 10 Oct 2023 11:53:06 -0500 Subject: [PATCH 02/15] fix: Change plan name from Enterprise to Premium on marketplace filtering (#30616) --- .changeset/heavy-ads-carry.md | 5 +++++ .../meteor/client/views/marketplace/hooks/useFilteredApps.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/heavy-ads-carry.md diff --git a/.changeset/heavy-ads-carry.md b/.changeset/heavy-ads-carry.md new file mode 100644 index 000000000000..c04e52fb48a0 --- /dev/null +++ b/.changeset/heavy-ads-carry.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: Change plan name from Enterprise to Premium on marketplace filtering diff --git a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts index 221990f7af2a..437c8d35207d 100644 --- a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts +++ b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts @@ -80,7 +80,7 @@ export const useFilteredApps = ({ explore: fallback, installed: fallback, private: fallback, - premium: (apps: App[]) => apps.filter(({ categories }) => categories.includes('Enterprise')), + premium: (apps: App[]) => apps.filter(({ categories }) => categories.includes('Premium')), requested: (apps: App[]) => apps.filter(({ appRequestStats, installed }) => Boolean(appRequestStats) && !installed), }; From f5c0d6b45db2828459f17e3c0f5bab238bf55b26 Mon Sep 17 00:00:00 2001 From: Heitor Tanoue <68477006+heitortanoue@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:45:21 -0300 Subject: [PATCH 03/15] fix: DM room stay "read only" after the deactivate and reactivate user with "guest" role (#30459) Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- .changeset/gentle-radios-relate.md | 5 +++++ apps/meteor/server/models/raw/Users.js | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/gentle-radios-relate.md diff --git a/.changeset/gentle-radios-relate.md b/.changeset/gentle-radios-relate.md new file mode 100644 index 000000000000..8d5f12b3a286 --- /dev/null +++ b/.changeset/gentle-radios-relate.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed DM room with "guest" user kept as "read only" after reactivating user diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 113f18ea83da..c8cee8f2f6bf 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -2187,7 +2187,6 @@ export class UsersRaw extends BaseRaw { { active: true, type: { $nin: ['app'] }, - roles: { $ne: ['guest'] }, _id: { $in: ids }, }, options, From 4855fa8eb29db1230834ef3ba00632a9cc1e5698 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Tue, 10 Oct 2023 15:36:53 -0300 Subject: [PATCH 04/15] refactor: AutoCompleteTagsMultiple to TS (#30577) --- apps/meteor/ee/client/hooks/useTagsList.ts | 9 ++++---- ...ltiple.js => AutoCompleteTagsMultiple.tsx} | 23 +++++++++++++++---- .../omnichannel/tags/CurrentChatTags.tsx | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) rename apps/meteor/ee/client/omnichannel/tags/{AutoCompleteTagsMultiple.js => AutoCompleteTagsMultiple.tsx} (68%) diff --git a/apps/meteor/ee/client/hooks/useTagsList.ts b/apps/meteor/ee/client/hooks/useTagsList.ts index 907fa96f4aab..41062d1b9dce 100644 --- a/apps/meteor/ee/client/hooks/useTagsList.ts +++ b/apps/meteor/ee/client/hooks/useTagsList.ts @@ -1,4 +1,3 @@ -import type { ILivechatTagRecord } from '@rocket.chat/core-typings'; import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback, useState } from 'react'; @@ -12,8 +11,10 @@ type TagsListOptions = { viewAll?: boolean; }; +type TagListItem = { _id: string; label: string; value: string; _updatedAt: Date }; + type UseTagsListResult = { - itemsList: RecordList; + itemsList: RecordList; initialItemCount: number; reload: () => void; loadMoreItems: (start: number, end: number) => void; @@ -21,8 +22,8 @@ type UseTagsListResult = { export const useTagsList = (options: TagsListOptions): UseTagsListResult => { const { viewAll, department, filter } = options; - const [itemsList, setItemsList] = useState(() => new RecordList()); - const reload = useCallback(() => setItemsList(new RecordList()), []); + const [itemsList, setItemsList] = useState(() => new RecordList()); + const reload = useCallback(() => setItemsList(new RecordList()), []); const getTags = useEndpoint('GET', '/v1/livechat/tags'); diff --git a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.tsx similarity index 68% rename from apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js rename to apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.tsx index db48060ff546..f0baca991e2e 100644 --- a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js +++ b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.tsx @@ -1,3 +1,4 @@ +import type { PaginatedMultiSelectOption } from '@rocket.chat/fuselage'; import { PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; @@ -7,9 +8,21 @@ import { useRecordList } from '../../../../client/hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState'; import { useTagsList } from '../../hooks/useTagsList'; -const AutoCompleteTagMultiple = (props) => { - const { value, onlyMyTags = false, onChange = () => {}, department, viewAll = false } = props; +type AutoCompleteTagsMultipleProps = { + value?: PaginatedMultiSelectOption[]; + onlyMyTags?: boolean; + onChange?: (value: PaginatedMultiSelectOption[]) => void; + department?: string; + viewAll?: boolean; +}; +const AutoCompleteTagsMultiple = ({ + value = [], + onlyMyTags = false, + onChange = () => undefined, + department, + viewAll = false, +}: AutoCompleteTagsMultipleProps) => { const t = useTranslation(); const [tagsFilter, setTagsFilter] = useState(''); @@ -41,9 +54,11 @@ const AutoCompleteTagMultiple = (props) => { flexShrink={0} flexGrow={0} placeholder={t('Select_an_option')} - endReached={tagsPhase === AsyncStatePhase.LOADING ? () => {} : (start) => loadMoreTags(start, Math.min(50, tagsTotal))} + endReached={ + tagsPhase === AsyncStatePhase.LOADING ? () => undefined : (start) => start && loadMoreTags(start, Math.min(50, tagsTotal)) + } /> ); }; -export default memo(AutoCompleteTagMultiple); +export default memo(AutoCompleteTagsMultiple); diff --git a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx index 61c1d11af947..553d31b7479d 100644 --- a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx +++ b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx @@ -3,7 +3,7 @@ import React from 'react'; import AutoCompleteTagsMultiple from './AutoCompleteTagsMultiple'; -type CurrentChatTagsProps = { value: Array; handler: () => void; department?: string; viewAll?: boolean }; +type CurrentChatTagsProps = { value: Array<{ value: string; label: string }>; handler: () => void; department?: string; viewAll?: boolean }; const CurrentChatTags: FC = ({ value, handler, department, viewAll }) => ( From 2124868d4f2934beae1ba7280e3fdf2189a6c369 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Wed, 11 Oct 2023 02:42:06 +0530 Subject: [PATCH 05/15] fix: reset file input after uploading file (#30529) --- .changeset/eleven-gorillas-deliver.md | 5 +++++ apps/meteor/client/lib/chats/ChatAPI.ts | 2 +- apps/meteor/client/lib/chats/flows/uploadFiles.ts | 3 ++- .../actions/FileUploadAction.tsx | 11 +++++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 .changeset/eleven-gorillas-deliver.md diff --git a/.changeset/eleven-gorillas-deliver.md b/.changeset/eleven-gorillas-deliver.md new file mode 100644 index 000000000000..403bd294828b --- /dev/null +++ b/.changeset/eleven-gorillas-deliver.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fix trying to upload same file again and again. diff --git a/apps/meteor/client/lib/chats/ChatAPI.ts b/apps/meteor/client/lib/chats/ChatAPI.ts index 1630071be658..8242de07d791 100644 --- a/apps/meteor/client/lib/chats/ChatAPI.ts +++ b/apps/meteor/client/lib/chats/ChatAPI.ts @@ -144,7 +144,7 @@ export type ChatAPI = { ActionManager: any; readonly flows: { - readonly uploadFiles: (files: readonly File[]) => Promise; + readonly uploadFiles: (files: readonly File[], resetFileInput?: () => void) => Promise; readonly sendMessage: ({ text, tshow }: { text: string; tshow?: boolean; previewUrls?: string[] }) => Promise; readonly processSlashCommand: (message: IMessage, userId: string | null) => Promise; readonly processTooLongMessage: (message: IMessage) => Promise; diff --git a/apps/meteor/client/lib/chats/flows/uploadFiles.ts b/apps/meteor/client/lib/chats/flows/uploadFiles.ts index 58eb18400a30..1411ad5a004e 100644 --- a/apps/meteor/client/lib/chats/flows/uploadFiles.ts +++ b/apps/meteor/client/lib/chats/flows/uploadFiles.ts @@ -6,7 +6,7 @@ import { imperativeModal } from '../../imperativeModal'; import { prependReplies } from '../../utils/prependReplies'; import type { ChatAPI } from '../ChatAPI'; -export const uploadFiles = async (chat: ChatAPI, files: readonly File[]): Promise => { +export const uploadFiles = async (chat: ChatAPI, files: readonly File[], resetFileInput?: () => void): Promise => { const replies = chat.composer?.quotedMessages.get() ?? []; const msg = await prependReplies('', replies); @@ -52,4 +52,5 @@ export const uploadFiles = async (chat: ChatAPI, files: readonly File[]): Promis }; uploadNextFile(); + resetFileInput?.(); }; diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/actions/FileUploadAction.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/actions/FileUploadAction.tsx index 73b293a60047..f9c826fceb4b 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/actions/FileUploadAction.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/actions/FileUploadAction.tsx @@ -18,6 +18,14 @@ const FileUploadAction = ({ collapsed, chatContext, disabled, ...props }: FileUp const fileInputRef = useRef(null); const chat = useChat() ?? chatContext; + const resetFileInput = () => { + if (!fileInputRef.current) { + return; + } + + fileInputRef.current.value = ''; + }; + const handleUploadChange = async (e: ChangeEvent) => { const { mime } = await import('../../../../../../../app/utils/lib/mimeTypes'); const filesToUpload = Array.from(e.target.files ?? []).map((file) => { @@ -26,8 +34,7 @@ const FileUploadAction = ({ collapsed, chatContext, disabled, ...props }: FileUp }); return file; }); - - chat?.flows.uploadFiles(filesToUpload); + chat?.flows.uploadFiles(filesToUpload, resetFileInput); }; const handleUpload = () => { From 03315b2f68f19e50ce40954ba448bf863fd4aac2 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 10 Oct 2023 16:01:04 -0600 Subject: [PATCH 06/15] test: Validate monitors data access (#30593) --- .../livechat-enterprise/server/lib/units.ts | 2 +- .../server/methods/getUnitsFromUserRoles.ts | 4 +- .../end-to-end/api/livechat/22-monitors.ts | 201 ++++++++++++++++++ 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts index 58a75abc943c..309834f2d1e8 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts @@ -8,7 +8,7 @@ async function hasUnits(): Promise { } // Units should't change really often, so we can cache the result -const memoizedHasUnits = mem(hasUnits, { maxAge: 10000 }); +const memoizedHasUnits = mem(hasUnits, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); export async function getUnitsFromUser(): Promise<{ [k: string]: any }[] | undefined> { if (!(await memoizedHasUnits())) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts index 47929f384d8f..18ed1b1ea646 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts @@ -14,8 +14,8 @@ async function getDepartmentsFromUserRoles(user: string): Promise { return (await LivechatDepartmentAgents.findByAgentId(user).toArray()).map((department) => department.departmentId); } -const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: 10000 }); -const memoizedGetDepartmentsFromUserRoles = mem(getDepartmentsFromUserRoles, { maxAge: 5000 }); +const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); +const memoizedGetDepartmentsFromUserRoles = mem(getDepartmentsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); export const getUnitsFromUser = async (user: string): Promise => { if (!user || (await hasAnyRoleAsync(user, ['admin', 'livechat-manager']))) { diff --git a/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts b/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts new file mode 100644 index 000000000000..d4c2fd59211c --- /dev/null +++ b/apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts @@ -0,0 +1,201 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; +import { expect } from 'chai'; +import { before, it, describe } from 'mocha'; + +import { getCredentials, api, request } from '../../../data/api-data'; +import { addOrRemoveAgentFromDepartment, createDepartment } from '../../../data/livechat/department'; +import { + createAgent, + createLivechatRoom, + createManager, + createVisitor, + getLivechatRoomInfo, + makeAgentAvailable, +} from '../../../data/livechat/rooms'; +import { createMonitor, createUnit } from '../../../data/livechat/units'; +import { updateSetting, updatePermission } from '../../../data/permissions.helper'; +import { password } from '../../../data/user'; +import { createUser, login, setUserActiveStatus } from '../../../data/users.helper'; +import { IS_EE } from '../../../e2e/config/constants'; + +type TestUser = { user: IUser; credentials: { 'X-Auth-Token': string; 'X-User-Id': string } }; + +(IS_EE ? describe : describe.skip)('Omnichannel - Monitors', () => { + let manager: TestUser; + let monitor: TestUser; + let noUnitDepartment: ILivechatDepartment; + let unitDepartment: ILivechatDepartment; + + before((done) => getCredentials(done)); + before(async () => { + await updateSetting('Livechat_accept_chats_with_no_agents', true); + await setUserActiveStatus('rocketchat.internal.admin.test', true); + await createAgent(); + await makeAgentAvailable(); + }); + before(async () => { + const user: IUser = await createUser(); + const userCredentials = await login(user.username, password); + if (!user.username) { + throw new Error('user not created'); + } + await createManager(user.username); + + manager = { + user, + credentials: userCredentials, + }; + }); + before(async () => { + const user: IUser = await createUser(); + const userCredentials = await login(user.username, password); + if (!user.username) { + throw new Error('user not created'); + } + await createMonitor(user.username); + + monitor = { + user, + credentials: userCredentials, + }; + }); + before(async () => { + noUnitDepartment = await createDepartment(); + unitDepartment = await createDepartment(); + + await createUnit(monitor.user._id, monitor.user.username!, [unitDepartment._id]); + }); + before(async () => { + await updatePermission('transfer-livechat-guest', ['admin', 'livechat-manager', 'livechat-agent', 'livechat-monitor']); + }); + + describe('Monitors & Rooms', () => { + it('should not return a room of a department that the monitor is not assigned to', async () => { + const visitor = await createVisitor(noUnitDepartment._id); + const room = await createLivechatRoom(visitor.token); + + const { body } = await request + .get(api('livechat/rooms')) + .set(monitor.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('rooms').that.is.an('array'); + expect(body.rooms.find((r: any) => r._id === room._id)).to.not.exist; + }); + it('should return a room of a department the monitor is assigned to', async () => { + const visitor = await createVisitor(unitDepartment._id); + const room = await createLivechatRoom(visitor.token); + + const { body } = await request + .get(api('livechat/rooms')) + .set(monitor.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('rooms').that.is.an('array'); + expect(body.rooms.find((r: any) => r._id === room._id)).to.exist; + }); + }); + + describe('Monitors & Departments', () => { + it('should not return a department that the monitor is not assigned to', async () => { + const { body } = await request + .get(api('livechat/department')) + .query({ onlyMyDepartments: true }) + .set(monitor.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('departments').that.is.an('array'); + expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist; + }); + it('should return a department that the monitor is assigned to', async () => { + const { body } = await request + .get(api('livechat/department')) + .query({ onlyMyDepartments: true }) + .set(monitor.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('departments').that.is.an('array'); + expect(body.departments.length).to.be.equal(1); + expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist; + }); + it('should return both created departments to a manager', async () => { + const { body } = await request + .get(api('livechat/department')) + .query({ onlyMyDepartments: true, sort: '{ "_updatedAt": 1 }' }) + .set(manager.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('departments').that.is.an('array'); + expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.exist; + expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist; + }); + it('should not return a department when monitor is only assigned as agent there', async () => { + await createAgent(monitor.user.username!); + await addOrRemoveAgentFromDepartment( + noUnitDepartment._id, + { agentId: monitor.user._id, username: monitor.user.username!, count: 0, order: 0 }, + true, + ); + + const { body } = await request + .get(api('livechat/department')) + .query({ onlyMyDepartments: true }) + .set(monitor.credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('departments').that.is.an('array'); + expect(body.departments.length).to.be.equal(1); + expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist; + }); + }); + + describe('Monitors & Forward', () => { + it('should successfully forward a room to another agent', async () => { + const visitor = await createVisitor(unitDepartment._id); + const room = await createLivechatRoom(visitor.token); + + const { body } = await request + .post(api('livechat/room.forward')) + .set(monitor.credentials) + .send({ + roomId: room._id, + userId: 'rocketchat.internal.admin.test', + }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('success', true); + + const room2 = await getLivechatRoomInfo(room._id); + + expect(room2).to.have.property('servedBy').that.is.an('object'); + expect(room2.servedBy).to.have.property('_id', 'rocketchat.internal.admin.test'); + }); + it('should successfully forward a room to a department', async () => { + const visitor = await createVisitor(noUnitDepartment._id); + const room = await createLivechatRoom(visitor.token); + + const { body } = await request + .post(api('livechat/room.forward')) + .set(monitor.credentials) + .send({ + roomId: room._id, + departmentId: unitDepartment._id, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('success', true); + + const room2 = await getLivechatRoomInfo(room._id); + expect(room2.departmentId).to.be.equal(unitDepartment._id); + }); + }); +}); From 223dce18a35bae9af242305d46645ae944290ebe Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 10 Oct 2023 20:00:07 -0300 Subject: [PATCH 07/15] fix: Auto-translate doesn't work on E2E rooms (#30369) Co-authored-by: Guilherme Jun Grillo <48109548+guijun13@users.noreply.github.com> Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com> --- .changeset/popular-actors-cheat.md | 6 ++++ .../server/methods/saveSettings.ts | 9 +++++- .../server/functions/saveRoomEncrypted.ts | 6 +++- .../hooks/roomActions/useE2EERoomAction.ts | 22 +++++++++++-- .../views/room/Header/icons/Encrypted.tsx | 19 +++++++++--- .../AutoTranslate/AutoTranslate.tsx | 18 +++++++++-- .../AutoTranslate/AutoTranslateWithData.tsx | 20 ++++++++++++ .../rocketchat-i18n/i18n/en.i18n.json | 16 +++++++--- .../rocketchat-i18n/i18n/pt-BR.i18n.json | 7 +++++ apps/meteor/server/models/raw/Rooms.ts | 10 ++++++ .../meteor/server/models/raw/Subscriptions.ts | 8 +++++ apps/meteor/tests/e2e/e2e-encryption.spec.ts | 7 +++-- .../tests/e2e/page-objects/home-channel.ts | 6 ++++ .../tests/end-to-end/api/00-autotranslate.js | 31 ++++++++++++++++++- .../model-typings/src/models/IRoomsModel.ts | 2 ++ .../src/models/ISubscriptionsModel.ts | 1 + 16 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 .changeset/popular-actors-cheat.md diff --git a/.changeset/popular-actors-cheat.md b/.changeset/popular-actors-cheat.md new file mode 100644 index 000000000000..aad5ec6ae638 --- /dev/null +++ b/.changeset/popular-actors-cheat.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Do not allow auto-translation to be enabled in E2E rooms diff --git a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts index 1ba5bcdfcd76..e396d78887a9 100644 --- a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts +++ b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts @@ -1,4 +1,4 @@ -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Rooms } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -46,6 +46,13 @@ Meteor.methods({ switch (field) { case 'autoTranslate': + const room = await Rooms.findE2ERoomById(rid, { projection: { _id: 1 } }); + if (room && value === '1') { + throw new Meteor.Error('error-e2e-enabled', 'Enabling auto-translation in E2E encrypted rooms is not allowed', { + method: 'saveAutoTranslateSettings', + }); + } + await Subscriptions.updateAutoTranslateById(subscription._id, value === '1'); if (!subscription.autoTranslateLanguage && options.defaultLanguage) { await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage); diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts index dc57307b1c4c..ed07540ba2b0 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts @@ -1,7 +1,7 @@ import { Message } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { isRegisterUser } from '@rocket.chat/core-typings'; -import { Rooms } from '@rocket.chat/models'; +import { Rooms, Subscriptions } from '@rocket.chat/models'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; @@ -25,5 +25,9 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean await Message.saveSystemMessage(type, rid, user.username, user); } + + if (encrypted) { + await Subscriptions.disableAutoTranslateByRoomId(rid); + } return update; }; diff --git a/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts b/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts index e1c3126985ae..73b0f34836e1 100644 --- a/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts +++ b/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts @@ -5,13 +5,15 @@ import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { e2e } from '../../../app/e2e/client/rocketchat.e2e'; -import { useRoom } from '../../views/room/contexts/RoomContext'; +import { dispatchToastMessage } from '../../lib/toast'; +import { useRoom, useRoomSubscription } from '../../views/room/contexts/RoomContext'; import type { RoomToolboxActionConfig } from '../../views/room/contexts/RoomToolboxContext'; import { useReactiveValue } from '../useReactiveValue'; export const useE2EERoomAction = () => { const enabled = useSetting('E2E_Enable', false); const room = useRoom(); + const subscription = useRoomSubscription(); const readyToEncrypt = useReactiveValue(useCallback(() => e2e.isReady(), [])) || room.encrypted; const permittedToToggleEncryption = usePermission('toggle-room-e2e-encryption', room._id); const permittedToEditRoom = usePermission('edit-room', room._id); @@ -21,8 +23,22 @@ export const useE2EERoomAction = () => { const toggleE2E = useEndpoint('POST', '/v1/rooms.saveRoomSettings'); - const action = useMutableCallback(() => { - void toggleE2E({ rid: room._id, encrypted: !room.encrypted }); + const action = useMutableCallback(async () => { + const { success } = await toggleE2E({ rid: room._id, encrypted: !room.encrypted }); + if (!success) { + return; + } + + dispatchToastMessage({ + type: 'success', + message: room.encrypted + ? t('E2E_Encryption_disabled_for_room', { roomName: room.name }) + : t('E2E_Encryption_enabled_for_room', { roomName: room.name }), + }); + + if (subscription?.autoTranslate) { + dispatchToastMessage({ type: 'success', message: t('AutoTranslate_Disabled_for_room', { roomName: room.name }) }); + } }); const enabledOnRoom = !!room.encrypted; diff --git a/apps/meteor/client/views/room/Header/icons/Encrypted.tsx b/apps/meteor/client/views/room/Header/icons/Encrypted.tsx index 35aecc1e2dfb..dbfda21f5b7a 100644 --- a/apps/meteor/client/views/room/Header/icons/Encrypted.tsx +++ b/apps/meteor/client/views/room/Header/icons/Encrypted.tsx @@ -2,20 +2,31 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import colors from '@rocket.chat/fuselage-tokens/colors'; import { HeaderState } from '@rocket.chat/ui-client'; -import { useSetting, usePermission, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; +import { useSetting, usePermission, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; +import { dispatchToastMessage } from '../../../../lib/toast'; + const Encrypted = ({ room }: { room: IRoom }) => { const t = useTranslation(); const e2eEnabled = useSetting('E2E_Enable'); - const toggleE2E = useMethod('saveRoomSettings'); + const toggleE2E = useEndpoint('POST', '/v1/rooms.saveRoomSettings'); const canToggleE2E = usePermission('toggle-room-e2e-encryption'); const encryptedLabel = canToggleE2E ? t('Encrypted_key_title') : t('Encrypted'); - const handleE2EClick = useMutableCallback(() => { + const handleE2EClick = useMutableCallback(async () => { if (!canToggleE2E) { return; } - toggleE2E(room._id, 'encrypted', !room?.encrypted); + + const { success } = await toggleE2E({ rid: room._id, encrypted: !room.encrypted }); + if (!success) { + return; + } + + dispatchToastMessage({ + type: 'success', + message: t('E2E_Encryption_disabled_for_room', { roomName: room.name }), + }); }); return e2eEnabled && room?.encrypted ? ( diff --git a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx index 6952b5b1dafe..ad1560d3078d 100644 --- a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx +++ b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx @@ -1,4 +1,4 @@ -import { FieldGroup, Field, FieldLabel, FieldRow, ToggleSwitch, Select } from '@rocket.chat/fuselage'; +import { Callout, FieldGroup, Field, FieldLabel, FieldRow, ToggleSwitch, Select } from '@rocket.chat/fuselage'; import type { SelectOption } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement, ChangeEvent } from 'react'; @@ -11,6 +11,7 @@ import { ContextualbarIcon, ContextualbarContent, } from '../../../../components/Contextualbar'; +import { useRoom } from '../../contexts/RoomContext'; type AutoTranslateProps = { language: string; @@ -30,6 +31,7 @@ const AutoTranslate = ({ handleClose, }: AutoTranslateProps): ReactElement => { const t = useTranslation(); + const room = useRoom(); return ( <> @@ -40,14 +42,24 @@ const AutoTranslate = ({ + {room.encrypted && ( + + {t('Automatic_translation_not_available_info')} + + )} - + {t('Automatic_Translation')} - {t('Language')} + {t('Translate_to')}