From d0862cb983d41f3f2d73115296a994d0bf2ff1c6 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Wed, 3 Apr 2024 11:19:19 -0300 Subject: [PATCH 01/16] test(livechat): fix Department flaky test (#32102) --- .../omnichannel-livechat-department.spec.ts | 70 +++++++++++++++---- .../e2e/page-objects/omnichannel-livechat.ts | 18 ++--- .../src/components/Modal/component.js | 7 +- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts index fe820a71f62c..c0b2bf8ae852 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts @@ -8,10 +8,7 @@ import { createAgent } from '../utils/omnichannel/agents'; import { addAgentToDepartment, createDepartment } from '../utils/omnichannel/departments'; import { test, expect } from '../utils/test'; -const firstUser = { - name: `${faker.person.firstName()} ${faker.string.uuid()}}`, - email: faker.internet.email(), -}; + test.use({ storageState: Users.user1.state }); @@ -54,8 +51,8 @@ test.describe('OC - Livechat - Department Flow', () => { }); test.afterEach(async ({ page }) => { - await poHomeOmnichannelAgent1.page?.close(); - await poHomeOmnichannelAgent2.page?.close(); + await poHomeOmnichannelAgent1?.page?.close(); + await poHomeOmnichannelAgent2?.page?.close(); await page.close(); }); @@ -67,9 +64,15 @@ test.describe('OC - Livechat - Department Flow', () => { }); test('OC - Livechat - Chat with Department', async () => { + + const guest = { + name: `${faker.person.firstName()} ${faker.string.nanoid(10)}}`, + email: faker.internet.email(), + }; + await test.step('expect start Chat with department', async () => { await poLiveChat.openAnyLiveChat(); - await poLiveChat.sendMessage(firstUser, false, departmentA.name); + await poLiveChat.sendMessage(guest, false, departmentA.name); await expect(poLiveChat.onlineAgentMessage).toBeVisible(); await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); @@ -77,7 +80,7 @@ test.describe('OC - Livechat - Department Flow', () => { }); await test.step('expect message to be received by department', async () => { - await poHomeOmnichannelAgent1.sidenav.openChat(firstUser.name); + await poHomeOmnichannelAgent1.sidenav.openChat(guest.name); await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toBeVisible(); await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toContainText('this_a_test_message_from_user'); }); @@ -89,34 +92,73 @@ test.describe('OC - Livechat - Department Flow', () => { }); test('OC - Livechat - Change Department', async () => { + + const guest = { + name: `${faker.person.firstName()} ${faker.string.nanoid(10)}}`, + email: faker.internet.email(), + + }; await test.step('expect start Chat with department', async () => { await poLiveChat.openAnyLiveChat(); - await poLiveChat.sendMessage(firstUser, false, departmentA.name); + await poLiveChat.sendMessage(guest, false, departmentA.name); await expect(poLiveChat.onlineAgentMessage).toBeVisible(); await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible(); }); + await test.step('expect message to be received by department 1', async () => { + await poHomeOmnichannelAgent1.sidenav.openChat(guest.name); + await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toBeVisible(); + await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toContainText('this_a_test_message_from_user'); + }); + + await test.step('expect message to be sent by department 1', async () => { + await poHomeOmnichannelAgent1.content.sendMessage('this_a_test_message_from_agent_department_1'); + await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent_department_1"')).toBeVisible(); + await poHomeOmnichannelAgent1.page.close(); + }); + await test.step('expect to change department', async () => { - await poLiveChat.changeDepartment(departmentB.name); + await poLiveChat.btnOptions.click(); + await poLiveChat.btnChangeDepartment.click(); + + await expect(poLiveChat.selectDepartment).toBeVisible(); + await poLiveChat.selectDepartment.selectOption({ label: departmentB.name }); + + await expect(poLiveChat.btnSendMessage('Start chat')).toBeEnabled(); + await poLiveChat.btnSendMessage('Start chat').click(); + + await expect(poLiveChat.livechatModal).toBeVisible(); + + await expect(poLiveChat.livechatModalText('Are you sure you want to switch the department?')).toBeVisible(); + await poLiveChat.btnYes.click(); + + await expect(poLiveChat.livechatModal).toBeVisible(); + + await expect(poLiveChat.livechatModalText('Department switched')).toBeVisible(); + await poLiveChat.btnOk.click(); // Expect keep chat history await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible(); // Expect user to have changed await expect(await poLiveChat.headerTitle.textContent()).toEqual(agent2.username); + + await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user_to_department_2'); + await poLiveChat.btnSendMessageToOnlineAgent.click(); + await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user_to_department_2"')).toBeVisible(); }); await test.step('expect message to be received by department', async () => { - await poHomeOmnichannelAgent2.sidenav.openChat(firstUser.name); + await poHomeOmnichannelAgent2.sidenav.openChat(guest.name); await expect(poHomeOmnichannelAgent2.content.lastUserMessage).toBeVisible(); - await expect(poHomeOmnichannelAgent2.content.lastUserMessage).toContainText('this_a_test_message_from_user'); + await expect(poHomeOmnichannelAgent2.content.lastUserMessage).toContainText('this_a_test_message_from_user_to_department_2'); }); await test.step('expect message to be sent by department', async () => { - await poHomeOmnichannelAgent2.content.sendMessage('this_a_test_message_from_agent'); - await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent"')).toBeVisible(); + await poHomeOmnichannelAgent2.content.sendMessage('this_a_test_message_from_agent_department_2'); + await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent_department_2"')).toBeVisible(); }); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts index 7099af127ee2..1029c8ba2819 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts @@ -59,16 +59,6 @@ export class OmnichannelLiveChat { return this.page.locator(`text="${message}"`); } - async changeDepartment (department: string): Promise { - await this.btnOptions.click(); - await this.btnChangeDepartment.click(); - await this.selectDepartment.waitFor({ state: 'visible' }); - await this.selectDepartment.selectOption({ label: department }); - await this.btnSendMessage('Start chat').click(); - await this.btnYes.click(); - await this.btnOk.click(); - } - async closeChat(): Promise { await this.btnOptions.click(); await this.btnCloseChat.click(); @@ -132,8 +122,12 @@ export class OmnichannelLiveChat { return this.page.locator('footer div div div:nth-child(3) button'); } - get firstAutoMessage(): Locator { - return this.page.locator('div.message-text__WwYco p'); + get livechatModal(): Locator { + return this.page.locator('[data-qa-type="modal-overlay"]'); + } + + livechatModalText(text: string): Locator { + return this.page.locator(`[data-qa-type="modal-overlay"] >> text=${text}`); } get fileUploadTarget(): Locator { diff --git a/packages/livechat/src/components/Modal/component.js b/packages/livechat/src/components/Modal/component.js index 195d4598f217..16f0df40d8ea 100644 --- a/packages/livechat/src/components/Modal/component.js +++ b/packages/livechat/src/components/Modal/component.js @@ -48,7 +48,12 @@ export class Modal extends Component { render = ({ children, animated, open, ...props }) => open ? ( -
+
{children}
From 0fcf1f0f1b3500bdcbb2b5bee7076629deb10a2d Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 3 Apr 2024 13:01:51 -0300 Subject: [PATCH 02/16] chore: Remove duplicated `ChannelDeletionTable` (#32114) --- .../info/Delete/ChannelDeletionTable.tsx | 78 ------------------- .../info/Delete/ChannelDeletionTableRow.tsx | 35 --------- .../ChannelDeletionTable.tsx | 4 +- 3 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx delete mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx deleted file mode 100644 index b04c0dbc7bf7..000000000000 --- a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import type { IRoom, Serialized } from '@rocket.chat/core-typings'; -import { Box, CheckBox } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; - -import { GenericTable, GenericTableHeaderCell, GenericTableBody, GenericTableHeader } from '../../../../../components/GenericTable'; -import { useSort } from '../../../../../components/GenericTable/hooks/useSort'; -import ChannelDeletionTableRow from './ChannelDeletionTableRow'; - -type ChannelDeletationTable = { - rooms: Serialized[]; - onToggleAllRooms: () => void; - onChangeRoomSelection: (room: Serialized) => void; - selectedRooms: { [key: string]: Serialized }; -}; - -const ChannelDeletionTable = ({ rooms, onChangeRoomSelection, selectedRooms, onToggleAllRooms }: ChannelDeletationTable) => { - const t = useTranslation(); - const { sortBy, sortDirection, setSort } = useSort<'name' | 'usersCount'>('name'); - - const selectedRoomsLength = Object.values(selectedRooms).filter(Boolean).length; - - const getSortedChannels = () => { - if (rooms) { - const sortedRooms = [...rooms]; - if (sortBy === 'name') { - sortedRooms.sort((a, b) => (a.name && b.name ? a.name.localeCompare(b.name) : 0)); - } - if (sortBy === 'usersCount') { - sortedRooms.sort((a, b) => a.usersCount - b.usersCount); - } - if (sortDirection === 'desc') { - return sortedRooms?.reverse(); - } - return sortedRooms; - } - }; - - const sortedRooms = getSortedChannels(); - - const checked = rooms.length === selectedRoomsLength; - const indeterminate = rooms.length > selectedRoomsLength && selectedRoomsLength > 0; - - const headers = ( - <> - - - {t('Channel_name')} - - - - {t('Members')} - - - - ); - - return ( - - - {headers} - - {sortedRooms?.map((room) => ( - - ))} - - - - ); -}; - -export default ChannelDeletionTable; diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx deleted file mode 100644 index 7531d4c2b3f5..000000000000 --- a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { IRoom, Serialized } from '@rocket.chat/core-typings'; -import { CheckBox, Margins } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React from 'react'; - -import { GenericTableRow, GenericTableCell } from '../../../../../components/GenericTable'; -import { RoomIcon } from '../../../../../components/RoomIcon'; - -type ChannelDeletionTableRowProps = { - room: Serialized; - onChange: (room: Serialized) => void; - selected: boolean; -}; - -const ChannelDeletionTableRow = ({ room, onChange, selected }: ChannelDeletionTableRowProps) => { - const { name, fname, usersCount } = room; - const handleChange = useMutableCallback(() => onChange(room)); - - return ( - - - - - - {fname ?? name} - - - - {usersCount} - - - ); -}; - -export default ChannelDeletionTableRow; diff --git a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTable.tsx b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTable.tsx index fb5bf144372b..4bd85aed663c 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTable.tsx +++ b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTable.tsx @@ -7,14 +7,14 @@ import { GenericTable, GenericTableHeaderCell, GenericTableBody, GenericTableHea import { useSort } from '../../../../../../components/GenericTable/hooks/useSort'; import ChannelDeletionTableRow from './ChannelDeletionTableRow'; -type ChannelDeletationTable = { +type ChannelDeletionTableProps = { rooms: Serialized[]; onToggleAllRooms: () => void; onChangeRoomSelection: (room: Serialized) => void; selectedRooms: { [key: string]: Serialized }; }; -const ChannelDeletionTable = ({ rooms, onChangeRoomSelection, selectedRooms, onToggleAllRooms }: ChannelDeletationTable) => { +const ChannelDeletionTable = ({ rooms, onChangeRoomSelection, selectedRooms, onToggleAllRooms }: ChannelDeletionTableProps) => { const t = useTranslation(); const { sortBy, sortDirection, setSort } = useSort<'name' | 'usersCount'>('name'); From 1626945fa3c4a9ade57fe09a479f442a5a51bc62 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 3 Apr 2024 14:59:33 -0600 Subject: [PATCH 03/16] fix: UI allowing to mark room as favorite despite room was not a `default` room (#32063) --- .changeset/pink-ants-sing.md | 6 ++ .../client/views/admin/rooms/EditRoom.tsx | 4 +- apps/meteor/tests/e2e/administration.spec.ts | 15 +++++ apps/meteor/tests/end-to-end/api/09-rooms.js | 61 ++++++++++++++++++- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 .changeset/pink-ants-sing.md diff --git a/.changeset/pink-ants-sing.md b/.changeset/pink-ants-sing.md new file mode 100644 index 000000000000..7b4841a11561 --- /dev/null +++ b/.changeset/pink-ants-sing.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a UI issue that allowed a user to "mark" a room as favorite even when a room was not default. The Back-End was correctly ignoring the `favorite` property from being updated when the room was not default, but the UI still allowed users to try. +As UI allowed but changes were not saved, this gave the impression that the function was not working. diff --git a/apps/meteor/client/views/admin/rooms/EditRoom.tsx b/apps/meteor/client/views/admin/rooms/EditRoom.tsx index 1522f4694160..cc165bca215b 100644 --- a/apps/meteor/client/views/admin/rooms/EditRoom.tsx +++ b/apps/meteor/client/views/admin/rooms/EditRoom.tsx @@ -86,7 +86,7 @@ const EditRoom = ({ room, onChange, onDelete }: EditRoomProps) => { canViewReactWhenReadOnly, } = useEditAdminRoomPermissions(room); - const { roomType, readOnly, archived } = watch(); + const { roomType, readOnly, archived, isDefault } = watch(); const changeArchiving = archived !== !!room.archived; @@ -324,7 +324,7 @@ const EditRoom = ({ room, onChange, onDelete }: EditRoomProps) => { name='favorite' control={control} render={({ field: { value, ...field } }) => ( - + )} /> diff --git a/apps/meteor/tests/e2e/administration.spec.ts b/apps/meteor/tests/e2e/administration.spec.ts index dcfb85373186..23d9d5214aaf 100644 --- a/apps/meteor/tests/e2e/administration.spec.ts +++ b/apps/meteor/tests/e2e/administration.spec.ts @@ -123,6 +123,21 @@ test.describe.parallel('administration', () => { await poAdmin.getRoomRow(targetChannel).click(); await expect(poAdmin.favoriteInput).toBeChecked(); }); + + test('should see favorite switch disabled when default is not true', async () => { + await poAdmin.inputSearchRooms.type(targetChannel); + await poAdmin.getRoomRow(targetChannel).click(); + await poAdmin.defaultLabel.click(); + + await expect(poAdmin.favoriteInput).toBeDisabled(); + }); + + test('should see favorite switch enabled when default is true', async () => { + await poAdmin.inputSearchRooms.type(targetChannel); + await poAdmin.getRoomRow(targetChannel).click(); + + await expect(poAdmin.favoriteInput).toBeEnabled(); + }); }); }); diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index e60cf8c1f6b4..92b73f34557e 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1742,7 +1742,7 @@ describe('[Rooms]', function () { }); }); - describe('/rooms.saveRoomSettings', () => { + describe('rooms.saveRoomSettings', () => { let testChannel; const randomString = `randomString${Date.now()}`; let discussion; @@ -1847,5 +1847,64 @@ describe('[Rooms]', function () { expect(res.body.room).to.have.property('fname', newDiscussionName); }); }); + + it('should mark a room as favorite', async () => { + await request + .post(api('rooms.saveRoomSettings')) + .set(credentials) + .send({ + rid: testChannel._id, + favorite: { + favorite: true, + defaultValue: true, + }, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + await request + .get(api('rooms.info')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('room').and.to.be.an('object'); + + expect(res.body.room).to.have.property('_id', testChannel._id); + expect(res.body.room).to.have.property('favorite', true); + }); + }); + it('should not mark a room as favorite when room is not a default room', async () => { + await request + .post(api('rooms.saveRoomSettings')) + .set(credentials) + .send({ + rid: testChannel._id, + favorite: { + favorite: true, + defaultValue: false, + }, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + await request + .get(api('rooms.info')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('room').and.to.be.an('object'); + + expect(res.body.room).to.have.property('_id', testChannel._id); + expect(res.body.room).to.not.have.property('favorite'); + }); + }); }); }); From c7fb2eba5db6bd3dd71c5785478600e52f33420e Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Wed, 3 Apr 2024 19:04:02 -0300 Subject: [PATCH 04/16] test: fix `should edit name of targetChannel` flaky test (#32121) Co-authored-by: Guilherme Gazzo --- apps/meteor/tests/e2e/channel-management.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts index efa39e0773b3..86bab6981346 100644 --- a/apps/meteor/tests/e2e/channel-management.spec.ts +++ b/apps/meteor/tests/e2e/channel-management.spec.ts @@ -130,6 +130,7 @@ test.describe.serial('channel-management', () => { await poHomeChannel.tabs.room.btnSave.click(); targetChannel = `NAME-EDITED-${targetChannel}`; + await expect(page.locator(`role=main >> role=heading[name="${targetChannel}"]`)).toBeVisible(); await poHomeChannel.sidenav.openChat(targetChannel); await expect(page).toHaveURL(`/channel/${targetChannel}`); From 3c5d4ffac539fbb59e31e98036e179123cb73594 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 3 Apr 2024 19:47:42 -0300 Subject: [PATCH 05/16] fix: search room not reactive after room name changes (#32123) --- .changeset/sweet-books-trade.md | 5 +++++ apps/meteor/client/sidebar/search/SearchList.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/sweet-books-trade.md diff --git a/.changeset/sweet-books-trade.md b/.changeset/sweet-books-trade.md new file mode 100644 index 000000000000..be828d662f32 --- /dev/null +++ b/.changeset/sweet-books-trade.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fixed search room not showing the new name room name changes diff --git a/apps/meteor/client/sidebar/search/SearchList.tsx b/apps/meteor/client/sidebar/search/SearchList.tsx index ceb89d3d7092..d215a77ce4bd 100644 --- a/apps/meteor/client/sidebar/search/SearchList.tsx +++ b/apps/meteor/client/sidebar/search/SearchList.tsx @@ -101,7 +101,7 @@ const useSearchItems = (filterText: string): UseQueryResult<(ISubscription & IRo const getSpotlight = useMethod('spotlight'); return useQuery( - ['sidebar/search/spotlight', name, usernamesFromClient, type, localRooms.map(({ _id }) => _id)], + ['sidebar/search/spotlight', name, usernamesFromClient, type, localRooms.map(({ _id, name }) => _id + name)], async () => { if (localRooms.length === LIMIT) { return localRooms; From aee039b2a26d119f95f28b2c8ab20debf68cced2 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 4 Apr 2024 14:13:15 -0300 Subject: [PATCH 06/16] refactor: `TeamsChannels` to typescript (#32109) --- .../roomActions/useTeamChannelsRoomAction.ts | 2 +- .../CreateChannel/CreateChannelModal.tsx | 4 +- .../AddExistingModal/AddExistingModal.tsx | 6 +- .../channels/BaseTeamsChannels.tsx | 156 ------------- .../ConfirmationModal/ConfirmationModal.tsx | 41 ---- .../channels/ConfirmationModal/index.ts | 1 - .../contextualBar/channels/RoomActions.js | 154 ------------- .../views/teams/contextualBar/channels/Row.js | 13 -- ...amsChannelItem.js => TeamsChannelItem.tsx} | 23 +- .../channels/TeamsChannelItemMenu.tsx | 59 +++++ .../contextualBar/channels/TeamsChannels.tsx | 218 +++++++++++------- .../channels/TeamsChannelsWithData.tsx | 73 ++++++ .../channels/hooks/useRemoveRoomFromTeam.tsx | 43 ++++ .../channels/hooks/useToggleAutoJoin.tsx | 23 ++ .../teams/contextualBar/channels/index.ts | 2 +- packages/i18n/src/locales/af.i18n.json | 1 - packages/i18n/src/locales/ar.i18n.json | 1 - packages/i18n/src/locales/az.i18n.json | 1 - packages/i18n/src/locales/be-BY.i18n.json | 1 - packages/i18n/src/locales/bg.i18n.json | 1 - packages/i18n/src/locales/bs.i18n.json | 1 - packages/i18n/src/locales/ca.i18n.json | 1 - packages/i18n/src/locales/cs.i18n.json | 1 - packages/i18n/src/locales/cy.i18n.json | 1 - packages/i18n/src/locales/da.i18n.json | 1 - packages/i18n/src/locales/de-AT.i18n.json | 1 - packages/i18n/src/locales/de-IN.i18n.json | 1 - packages/i18n/src/locales/de.i18n.json | 1 - packages/i18n/src/locales/el.i18n.json | 1 - packages/i18n/src/locales/en.i18n.json | 1 - packages/i18n/src/locales/eo.i18n.json | 1 - packages/i18n/src/locales/es.i18n.json | 1 - packages/i18n/src/locales/fa.i18n.json | 1 - packages/i18n/src/locales/fi.i18n.json | 1 - packages/i18n/src/locales/fr.i18n.json | 1 - packages/i18n/src/locales/he.i18n.json | 1 - packages/i18n/src/locales/hr.i18n.json | 1 - packages/i18n/src/locales/hu.i18n.json | 1 - packages/i18n/src/locales/id.i18n.json | 1 - packages/i18n/src/locales/it.i18n.json | 1 - packages/i18n/src/locales/ja.i18n.json | 1 - packages/i18n/src/locales/ka-GE.i18n.json | 1 - packages/i18n/src/locales/km.i18n.json | 1 - packages/i18n/src/locales/ko.i18n.json | 1 - packages/i18n/src/locales/ku.i18n.json | 1 - packages/i18n/src/locales/lo.i18n.json | 1 - packages/i18n/src/locales/lt.i18n.json | 1 - packages/i18n/src/locales/lv.i18n.json | 1 - packages/i18n/src/locales/mn.i18n.json | 1 - packages/i18n/src/locales/ms-MY.i18n.json | 1 - packages/i18n/src/locales/nl.i18n.json | 1 - packages/i18n/src/locales/no.i18n.json | 1 - packages/i18n/src/locales/pl.i18n.json | 1 - packages/i18n/src/locales/pt-BR.i18n.json | 1 - packages/i18n/src/locales/pt.i18n.json | 1 - packages/i18n/src/locales/ro.i18n.json | 1 - packages/i18n/src/locales/ru.i18n.json | 1 - packages/i18n/src/locales/sk-SK.i18n.json | 1 - packages/i18n/src/locales/sl-SI.i18n.json | 1 - packages/i18n/src/locales/sq.i18n.json | 1 - packages/i18n/src/locales/sr.i18n.json | 1 - packages/i18n/src/locales/sv.i18n.json | 1 - packages/i18n/src/locales/ta-IN.i18n.json | 1 - packages/i18n/src/locales/th-TH.i18n.json | 1 - packages/i18n/src/locales/tr.i18n.json | 1 - packages/i18n/src/locales/ug.i18n.json | 1 - packages/i18n/src/locales/uk.i18n.json | 1 - packages/i18n/src/locales/vi-VN.i18n.json | 1 - packages/i18n/src/locales/zh-HK.i18n.json | 1 - packages/i18n/src/locales/zh-TW.i18n.json | 1 - packages/i18n/src/locales/zh.i18n.json | 1 - 71 files changed, 362 insertions(+), 512 deletions(-) delete mode 100644 apps/meteor/client/views/teams/contextualBar/channels/BaseTeamsChannels.tsx delete mode 100644 apps/meteor/client/views/teams/contextualBar/channels/ConfirmationModal/ConfirmationModal.tsx delete mode 100644 apps/meteor/client/views/teams/contextualBar/channels/ConfirmationModal/index.ts delete mode 100644 apps/meteor/client/views/teams/contextualBar/channels/RoomActions.js delete mode 100644 apps/meteor/client/views/teams/contextualBar/channels/Row.js rename apps/meteor/client/views/teams/contextualBar/channels/{TeamsChannelItem.js => TeamsChannelItem.tsx} (77%) create mode 100644 apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelItemMenu.tsx create mode 100644 apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx create mode 100644 apps/meteor/client/views/teams/contextualBar/channels/hooks/useRemoveRoomFromTeam.tsx create mode 100644 apps/meteor/client/views/teams/contextualBar/channels/hooks/useToggleAutoJoin.tsx diff --git a/apps/meteor/client/hooks/roomActions/useTeamChannelsRoomAction.ts b/apps/meteor/client/hooks/roomActions/useTeamChannelsRoomAction.ts index 5876a88f2985..6fddd89f2dce 100644 --- a/apps/meteor/client/hooks/roomActions/useTeamChannelsRoomAction.ts +++ b/apps/meteor/client/hooks/roomActions/useTeamChannelsRoomAction.ts @@ -2,7 +2,7 @@ import { lazy, useMemo } from 'react'; import type { RoomToolboxActionConfig } from '../../views/room/contexts/RoomToolboxContext'; -const TeamsChannels = lazy(() => import('../../views/teams/contextualBar/channels/TeamsChannels')); +const TeamsChannels = lazy(() => import('../../views/teams/contextualBar/channels')); export const useTeamChannelsRoomAction = () => { return useMemo( diff --git a/apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx b/apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx index cd5e19947991..5738798f194e 100644 --- a/apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx +++ b/apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx @@ -35,6 +35,7 @@ import { useEncryptedRoomDescription } from '../hooks/useEncryptedRoomDescriptio type CreateChannelModalProps = { teamId?: string; onClose: () => void; + reload?: () => void; }; type CreateChannelModalPayload = { @@ -58,7 +59,7 @@ const getFederationHintKey = (licenseModule: ReturnType { +const CreateChannelModal = ({ teamId = '', onClose, reload }: CreateChannelModalProps): ReactElement => { const t = useTranslation(); const canSetReadOnly = usePermissionWithScopedRoles('set-readonly', ['owner']); const e2eEnabled = useSetting('E2E_Enable'); @@ -173,6 +174,7 @@ const CreateChannelModal = ({ teamId = '', onClose }: CreateChannelModalProps): } dispatchToastMessage({ type: 'success', message: t('Room_has_been_created') }); + reload?.(); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } finally { diff --git a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx index 4fa5828d462f..4917304f33fe 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx +++ b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx @@ -8,9 +8,10 @@ import RoomsAvailableForTeamsAutoComplete from './RoomsAvailableForTeamsAutoComp type AddExistingModalProps = { teamId: string; onClose: () => void; + reload?: () => void; }; -const AddExistingModal = ({ onClose, teamId }: AddExistingModalProps) => { +const AddExistingModal = ({ teamId, onClose, reload }: AddExistingModalProps) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); @@ -31,13 +32,14 @@ const AddExistingModal = ({ onClose, teamId }: AddExistingModalProps) => { }); dispatchToastMessage({ type: 'success', message: t('Channels_added') }); + reload?.(); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } finally { onClose(); } }, - [addRoomEndpoint, teamId, onClose, dispatchToastMessage, t], + [addRoomEndpoint, teamId, onClose, dispatchToastMessage, reload, t], ); return ( diff --git a/apps/meteor/client/views/teams/contextualBar/channels/BaseTeamsChannels.tsx b/apps/meteor/client/views/teams/contextualBar/channels/BaseTeamsChannels.tsx deleted file mode 100644 index ed0a83d39fce..000000000000 --- a/apps/meteor/client/views/teams/contextualBar/channels/BaseTeamsChannels.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import type { SelectOption } from '@rocket.chat/fuselage'; -import { Box, Icon, TextInput, Margins, Select, Throbber, ButtonGroup, Button } from '@rocket.chat/fuselage'; -import { useMutableCallback, useAutoFocus, useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ChangeEvent, Dispatch, SetStateAction, SyntheticEvent } from 'react'; -import React, { useMemo } from 'react'; -import { Virtuoso } from 'react-virtuoso'; - -import { - ContextualbarHeader, - ContextualbarIcon, - ContextualbarTitle, - ContextualbarClose, - ContextualbarContent, - ContextualbarFooter, - ContextualbarEmptyContent, -} from '../../../../components/Contextualbar'; -import { VirtuosoScrollbars } from '../../../../components/CustomScrollbars'; -import InfiniteListAnchor from '../../../../components/InfiniteListAnchor'; -import Row from './Row'; - -type BaseTeamsChannelsProps = { - loading: boolean; - channels: IRoom[]; - text: string; - type: 'all' | 'autoJoin'; - setType: Dispatch>; - setText: (e: ChangeEvent) => void; - onClickClose: () => void; - onClickAddExisting: false | ((e: SyntheticEvent) => void); - onClickCreateNew: false | ((e: SyntheticEvent) => void); - total: number; - loadMoreItems: (start: number, end: number) => void; - onClickView: (room: IRoom) => void; - reload: () => void; -}; - -const BaseTeamsChannels = ({ - loading, - channels = [], - text, - type, - setText, - setType, - onClickClose, - onClickAddExisting, - onClickCreateNew, - total, - loadMoreItems, - onClickView, - reload, -}: BaseTeamsChannelsProps) => { - const t = useTranslation(); - const inputRef = useAutoFocus(true); - - const options: SelectOption[] = useMemo( - () => [ - ['all', t('All')], - ['autoJoin', t('Team_Auto-join')], - ], - [t], - ); - - const lm = useMutableCallback((start) => !loading && loadMoreItems(start, Math.min(50, total - start))); - - const loadMoreChannels = useDebouncedCallback( - () => { - if (channels.length >= total) { - return; - } - - lm(channels.length); - }, - 300, - [lm, channels], - ); - - return ( - <> - - - {t('Team_Channels')} - {onClickClose && } - - - - - - - } - /> - - setType(val as 'all' | 'autoJoin')} value={type} options={options} /> + + + + + {loading && ( + + + + )} + {!loading && channels.length === 0 && } + {!loading && channels.length > 0 && ( + <> + + + {t('Showing')}: {channels.length} + + + + {t('Total')}: {total} + + + + }} + itemContent={(index, data) => } + /> + + + )} + + {(onClickAddExisting || onClickCreateNew) && ( + + + {onClickAddExisting && ( + + )} + {onClickCreateNew && ( + + )} + + + )} + ); }; diff --git a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx new file mode 100644 index 000000000000..965414400ee5 --- /dev/null +++ b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx @@ -0,0 +1,73 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { useLocalStorage, useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, usePermission } from '@rocket.chat/ui-contexts'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { useRecordList } from '../../../../hooks/lists/useRecordList'; +import { AsyncStatePhase } from '../../../../lib/asyncState'; +import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; +import CreateChannelWithData from '../../../../sidebar/header/CreateChannel'; +import { useRoom } from '../../../room/contexts/RoomContext'; +import { useRoomToolbox } from '../../../room/contexts/RoomToolboxContext'; +import AddExistingModal from './AddExistingModal'; +import TeamsChannels from './TeamsChannels'; +import { useTeamsChannelList } from './hooks/useTeamsChannelList'; + +const TeamsChannelsWithData = () => { + const room = useRoom(); + const setModal = useSetModal(); + const { closeTab } = useRoomToolbox(); + const canAddExistingTeam = usePermission('add-team-channel', room._id); + + const { teamId } = room; + + if (!teamId) { + throw new Error('Invalid teamId'); + } + + const [type, setType] = useLocalStorage<'all' | 'autoJoin'>('channels-list-type', 'all'); + const [text, setText] = useState(''); + const debouncedText = useDebouncedValue(text, 800); + + const { teamsChannelList, loadMoreItems, reload } = useTeamsChannelList( + useMemo(() => ({ teamId, text: debouncedText, type }), [teamId, debouncedText, type]), + ); + + const { phase, items, itemCount: total } = useRecordList(teamsChannelList); + + const handleTextChange = useCallback((event) => { + setText(event.currentTarget.value); + }, []); + + const handleAddExisting = useEffectEvent(() => { + setModal( setModal(null)} reload={reload} />); + }); + + const handleCreateNew = useEffectEvent(() => { + setModal( setModal(null)} reload={reload} />); + }); + + const goToRoom = useEffectEvent((room: IRoom) => { + roomCoordinator.openRouteLink(room.t, room); + }); + + return ( + + ); +}; + +export default TeamsChannelsWithData; diff --git a/apps/meteor/client/views/teams/contextualBar/channels/hooks/useRemoveRoomFromTeam.tsx b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useRemoveRoomFromTeam.tsx new file mode 100644 index 000000000000..de68070d2061 --- /dev/null +++ b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useRemoveRoomFromTeam.tsx @@ -0,0 +1,43 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { useEndpoint, usePermission, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import GenericModal from '../../../../../components/GenericModal'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; + +export const useRemoveRoomFromTeam = (room: IRoom, { reload }: { reload?: () => void }) => { + const t = useTranslation(); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const canRemoveTeamChannel = usePermission('remove-team-channel', room._id); + + const removeRoomEndpoint = useEndpoint('POST', '/v1/teams.removeRoom'); + + const handleRemoveRoom = () => { + const onConfirmAction = async () => { + if (!room.teamId) { + return; + } + + try { + await removeRoomEndpoint({ teamId: room.teamId, roomId: room._id }); + dispatchToastMessage({ type: 'error', message: t('Room_has_been_removed') }); + reload?.(); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setModal(null); + } + }; + + return setModal( + setModal(null)} onConfirm={onConfirmAction} confirmText={t('Remove')}> + {t('Team_Remove_from_team_modal_content', { + teamName: roomCoordinator.getRoomName(room.t, room), + })} + , + ); + }; + + return { handleRemoveRoom, canRemoveTeamChannel }; +}; diff --git a/apps/meteor/client/views/teams/contextualBar/channels/hooks/useToggleAutoJoin.tsx b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useToggleAutoJoin.tsx new file mode 100644 index 000000000000..3a437cd8e5c7 --- /dev/null +++ b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useToggleAutoJoin.tsx @@ -0,0 +1,23 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { useEndpoint, usePermission, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; + +export const useToggleAutoJoin = (room: IRoom, { reload }: { reload?: () => void }) => { + const dispatchToastMessage = useToastMessageDispatch(); + const updateRoomEndpoint = useEndpoint('POST', '/v1/teams.updateRoom'); + const canEditTeamChannel = usePermission('edit-team-channel', room._id); + + const handleToggleAutoJoin = async () => { + try { + await updateRoomEndpoint({ + roomId: room._id, + isDefault: !room.teamDefault, + }); + dispatchToastMessage({ type: 'success', message: room.teamDefault ? 'channel set as non autojoin' : 'channel set as autojoin' }); + reload?.(); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + + return { handleToggleAutoJoin, canEditTeamChannel }; +}; diff --git a/apps/meteor/client/views/teams/contextualBar/channels/index.ts b/apps/meteor/client/views/teams/contextualBar/channels/index.ts index 12523fd3815d..cd6564dfd512 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/index.ts +++ b/apps/meteor/client/views/teams/contextualBar/channels/index.ts @@ -1 +1 @@ -export { default } from './TeamsChannels'; +export { default } from './TeamsChannelsWithData'; diff --git a/packages/i18n/src/locales/af.i18n.json b/packages/i18n/src/locales/af.i18n.json index 05926a54689a..992c74fa1174 100644 --- a/packages/i18n/src/locales/af.i18n.json +++ b/packages/i18n/src/locales/af.i18n.json @@ -2102,7 +2102,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Dit is 'n verstekkanaal en verander dit na 'n privaat groep. Dit sal veroorsaak dat dit nie meer 'n verstekkanaal is nie. Wil jy voortgaan?", "Room_description_changed_successfully": "Kamerbeskrywing suksesvol verander", "Room_has_been_archived": "Kamer is geargiveer", - "Room_has_been_deleted": "Kamer is verwyder", "Room_has_been_unarchived": "Kamer is gearchiveer", "Room_Info": "Kamerinligting", "room_is_blocked": "Hierdie kamer is geblokkeer", diff --git a/packages/i18n/src/locales/ar.i18n.json b/packages/i18n/src/locales/ar.i18n.json index 214a1940a388..64772c8f495d 100644 --- a/packages/i18n/src/locales/ar.i18n.json +++ b/packages/i18n/src/locales/ar.i18n.json @@ -3618,7 +3618,6 @@ "room_disallowed_reacting": "غير مسموح لـ Room بالتفاعل من قِبل {{user_by}}", "Room_Edit": "تحرير Room", "Room_has_been_archived": "تمت أرشفة Room", - "Room_has_been_deleted": "تم حذف Room", "Room_has_been_removed": "تمت إزالة Room", "Room_has_been_unarchived": "تم إلغاء أرشفة Room", "Room_Info": "معلومات Room", diff --git a/packages/i18n/src/locales/az.i18n.json b/packages/i18n/src/locales/az.i18n.json index 513bd976f2f6..a61bb8bbff00 100644 --- a/packages/i18n/src/locales/az.i18n.json +++ b/packages/i18n/src/locales/az.i18n.json @@ -2102,7 +2102,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Bu standart bir kanaldır və onu xüsusi bir qrupa dəyişir, artıq bir default kanal olmayacaq. Davam etmək istəyirsinizmi?", "Room_description_changed_successfully": "Otaq təsviri uğurla dəyişdi", "Room_has_been_archived": "Otaq arxivləşdirildi", - "Room_has_been_deleted": "Otaq silindi", "Room_has_been_unarchived": "Otaq arxivləşdirilmişdir", "Room_Info": "Otaq məlumatı", "room_is_blocked": "Bu otaq blokdur", diff --git a/packages/i18n/src/locales/be-BY.i18n.json b/packages/i18n/src/locales/be-BY.i18n.json index 5c9fb33cc0d8..865efa0f9c5a 100644 --- a/packages/i18n/src/locales/be-BY.i18n.json +++ b/packages/i18n/src/locales/be-BY.i18n.json @@ -2119,7 +2119,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Гэта канал па змаўчанні і змяніць яго да прыватнай групе прымусіць яго больш не будзе каналам па змаўчанні. Вы хочаце працягнуць?", "Room_description_changed_successfully": "Апісанне нумароў паспяхова зменены", "Room_has_been_archived": "Нумар знаходзіцца ў архіве", - "Room_has_been_deleted": "Нумар быў выдалены", "Room_has_been_unarchived": "Нумар быў разархіваваць", "Room_Info": "пакой інфармацыя", "room_is_blocked": "Гэты нумар заблякаваны", diff --git a/packages/i18n/src/locales/bg.i18n.json b/packages/i18n/src/locales/bg.i18n.json index 171f8c32ce4f..318a310eed56 100644 --- a/packages/i18n/src/locales/bg.i18n.json +++ b/packages/i18n/src/locales/bg.i18n.json @@ -2099,7 +2099,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Това е канал по подразбиране и ако го промените на частна група, той вече няма да бъде канал по подразбиране. Искаш ли да продължиш?", "Room_description_changed_successfully": "Описание на стаята се промени успешно", "Room_has_been_archived": "Стаята е архивирана", - "Room_has_been_deleted": "Стаята е изтрита", "Room_has_been_unarchived": "Стаята е деархивирана", "Room_Info": "Информация за стаята", "room_is_blocked": "Тази стая е блокирана", diff --git a/packages/i18n/src/locales/bs.i18n.json b/packages/i18n/src/locales/bs.i18n.json index 4fe15d1e24ae..86fd7083b2ce 100644 --- a/packages/i18n/src/locales/bs.i18n.json +++ b/packages/i18n/src/locales/bs.i18n.json @@ -2095,7 +2095,6 @@ "Room_default_change_to_private_will_be_default_no_more": "To je zadani kanal i mijenja se u privatnu grupu, jer više neće biti zadani kanal. Želiš li nastaviti?", "Room_description_changed_successfully": "Opis sobe je uspješno promjenjen", "Room_has_been_archived": "Soba je arhivirana", - "Room_has_been_deleted": "Soba je obrisana", "Room_has_been_unarchived": "Soba je vraćena iz arhive", "Room_Info": "Info Sobe", "room_is_blocked": "Ova soba je blokirana", diff --git a/packages/i18n/src/locales/ca.i18n.json b/packages/i18n/src/locales/ca.i18n.json index 56b5713d155d..834ad853ff0f 100644 --- a/packages/i18n/src/locales/ca.i18n.json +++ b/packages/i18n/src/locales/ca.i18n.json @@ -3553,7 +3553,6 @@ "room_disallowed_reacting": "Room no permesa reaccionant per {{user_by}}", "Room_Edit": "Editar Room ", "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 Room s'ha desarxivat", "Room_Info": "Informació de la Room", diff --git a/packages/i18n/src/locales/cs.i18n.json b/packages/i18n/src/locales/cs.i18n.json index 32a127396509..4f1eb948651b 100644 --- a/packages/i18n/src/locales/cs.i18n.json +++ b/packages/i18n/src/locales/cs.i18n.json @@ -3024,7 +3024,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Tato místnost je výchozí a pokud bude změněna na privátní, již nebude moci být mezi výchozími. Přesto změnit?", "Room_description_changed_successfully": "Popis místnosti změněn", "Room_has_been_archived": "Místnost byla archivována", - "Room_has_been_deleted": "Místnost smazána", "Room_has_been_unarchived": "Místnost již není archivována", "Room_Info": "Informace o místnosti", "room_is_blocked": "Místnost je blokována", diff --git a/packages/i18n/src/locales/cy.i18n.json b/packages/i18n/src/locales/cy.i18n.json index 38cb42a46eb3..f9f99d0e6423 100644 --- a/packages/i18n/src/locales/cy.i18n.json +++ b/packages/i18n/src/locales/cy.i18n.json @@ -2097,7 +2097,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Sianel ddiofyn yw hon a'i newid i grŵp preifat fydd yn achosi iddo beidio â bod yn sianel ddiofyn mwyach. Ydych chi am fynd ymlaen?", "Room_description_changed_successfully": "Newidiodd disgrifiad ystafell yn llwyddiannus", "Room_has_been_archived": "Mae'r ystafell wedi'i archifo", - "Room_has_been_deleted": "Mae'r ystafell wedi'i ddileu", "Room_has_been_unarchived": "Mae'r ystafell wedi ei anwybyddu", "Room_Info": "Gwybodaeth Ystafell", "room_is_blocked": "Mae'r ystafell hon wedi'i rhwystro", diff --git a/packages/i18n/src/locales/da.i18n.json b/packages/i18n/src/locales/da.i18n.json index 748e6e3f1fc1..789fa5347410 100644 --- a/packages/i18n/src/locales/da.i18n.json +++ b/packages/i18n/src/locales/da.i18n.json @@ -3125,7 +3125,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Dette er en standardkanal og ændrer den til en privat gruppe, fordi den ikke længere er en standardkanal. Vil du fortsætte?", "Room_description_changed_successfully": "Værelsesbeskrivelsen er ændret", "Room_has_been_archived": "Værelset er blevet arkiveret", - "Room_has_been_deleted": "Værelset er blevet slettet", "Room_has_been_unarchived": "Værelset er blevet arkiveret", "Room_Info": "Info om rummet", "room_is_blocked": "Dette rum er blokeret", diff --git a/packages/i18n/src/locales/de-AT.i18n.json b/packages/i18n/src/locales/de-AT.i18n.json index b4073551357c..b41505392150 100644 --- a/packages/i18n/src/locales/de-AT.i18n.json +++ b/packages/i18n/src/locales/de-AT.i18n.json @@ -2105,7 +2105,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Dies ist ein Standardkanal und wenn Sie ihn in eine private Gruppe ändern, wird er nicht mehr als Standardkanal angezeigt. Willst du fortfahren?", "Room_description_changed_successfully": "Raumbeschreibung erfolgreich geändert", "Room_has_been_archived": "Das Zimmer wurde archiviert", - "Room_has_been_deleted": "Der Raum wurde gelöscht.", "Room_has_been_unarchived": "Der Raum wurde entfernt", "Room_Info": "Raum", "room_is_blocked": "Dieser Raum ist blockiert", diff --git a/packages/i18n/src/locales/de-IN.i18n.json b/packages/i18n/src/locales/de-IN.i18n.json index 109df203897a..719cfe9da4fd 100644 --- a/packages/i18n/src/locales/de-IN.i18n.json +++ b/packages/i18n/src/locales/de-IN.i18n.json @@ -2390,7 +2390,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Das ist ein Standardkanal. Die Änderung zu einem privaten Kanal führt dazu, dass er dies nicht mehr ist. Willst Du das?", "Room_description_changed_successfully": "Raumbeschreibung erfolgreich geändert", "Room_has_been_archived": "Der Raum wurde archiviert", - "Room_has_been_deleted": "Der Raum wurde gelöscht", "Room_has_been_unarchived": "Der Raum wurde aus dem Archiv geholt", "Room_Info": "Rauminformation", "room_is_blocked": "Der Raum ist blockiert", diff --git a/packages/i18n/src/locales/de.i18n.json b/packages/i18n/src/locales/de.i18n.json index 2ce24e2aa0e4..64e30aeec0fc 100644 --- a/packages/i18n/src/locales/de.i18n.json +++ b/packages/i18n/src/locales/de.i18n.json @@ -4062,7 +4062,6 @@ "Room_has_been_archived": "Der Room wurde archiviert", "Room_has_been_converted": "Room wurde konvertiert", "Room_has_been_created": "Room wurde erstellt", - "Room_has_been_deleted": "Der Room wurde gelöscht", "Room_has_been_removed": "Raum wurde entfernt", "Room_has_been_unarchived": "Der Room wurde aus dem Archiv geholt", "Room_Info": "Room-Information", diff --git a/packages/i18n/src/locales/el.i18n.json b/packages/i18n/src/locales/el.i18n.json index 0c52b2df3259..4cf3b7764dd0 100644 --- a/packages/i18n/src/locales/el.i18n.json +++ b/packages/i18n/src/locales/el.i18n.json @@ -2110,7 +2110,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Αυτό είναι ένα προεπιλεγμένο κανάλι και η αλλαγή του σε μια ιδιωτική ομάδα θα το κάνει να μην είναι πλέον προεπιλεγμένο κανάλι. Θέλετε να συνεχίσετε?", "Room_description_changed_successfully": "Η περιγραφή δωματίου άλλαξε με επιτυχία", "Room_has_been_archived": "Η αίθουσα έχει αρχειοθετηθεί", - "Room_has_been_deleted": "Δωμάτιο έχει διαγραφεί", "Room_has_been_unarchived": "Η αίθουσα έχει αφαιρεθεί", "Room_Info": "Πληροφορίες δωματίου", "room_is_blocked": "Αυτό το δωμάτιο έχει αποκλειστεί", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index bd3222cdce93..bfdb3aa65236 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -4519,7 +4519,6 @@ "Room_has_been_archived": "Room has been archived", "Room_has_been_converted": "Room has been converted", "Room_has_been_created": "Room has been created", - "Room_has_been_deleted": "Room has been deleted", "Room_has_been_removed": "Room has been removed", "Room_has_been_unarchived": "Room has been unarchived", "Room_Info": "Room Information", diff --git a/packages/i18n/src/locales/eo.i18n.json b/packages/i18n/src/locales/eo.i18n.json index f20d82eb9682..5631b9240ffa 100644 --- a/packages/i18n/src/locales/eo.i18n.json +++ b/packages/i18n/src/locales/eo.i18n.json @@ -2102,7 +2102,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ĉi tiu estas defaŭlta kanalo kaj ŝanĝanta ĝin al privata grupo kaŭzos ke ĝi ne plu estos normala kanalo. Ĉu vi volas daŭrigi?", "Room_description_changed_successfully": "Salono priskribo ŝanĝiĝis sukcese", "Room_has_been_archived": "Salono estis arkivita", - "Room_has_been_deleted": "Salono estis forigita", "Room_has_been_unarchived": "Salono estis neatendita", "Room_Info": "Salono Informoj", "room_is_blocked": "Ĉi tiu ĉambro estas blokita", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index 8eca2a2aec15..9bb24537f555 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -3605,7 +3605,6 @@ "room_disallowed_reacting": "Room no permite reacciones por {{user_by}}", "Room_Edit": "Editar Room ", "Room_has_been_archived": "La Room se ha archivado", - "Room_has_been_deleted": "La Room se ha eliminado", "Room_has_been_removed": "La Room se ha eliminado", "Room_has_been_unarchived": "La Room se ha desarchivado", "Room_Info": "Información de Room", diff --git a/packages/i18n/src/locales/fa.i18n.json b/packages/i18n/src/locales/fa.i18n.json index cfeb59e37ddd..c812d39a543a 100644 --- a/packages/i18n/src/locales/fa.i18n.json +++ b/packages/i18n/src/locales/fa.i18n.json @@ -2413,7 +2413,6 @@ "Room_default_change_to_private_will_be_default_no_more": "این یک کانال پیشفرض است و تغییر آن به یک گروه خصوصی سبب میشود دیگر کانال پیشفرض نباشد. آیا شما می خواهید ادامه دهید؟", "Room_description_changed_successfully": "توضیحات اتاق با موفقیت تغییر کرد", "Room_has_been_archived": "اتاق بایگانی شده است", - "Room_has_been_deleted": "اتاق حذف شده است", "Room_has_been_unarchived": "اتاق آرام شده است", "Room_Info": "اطلاعات اتاق", "room_is_blocked": "این اتاق مسدود شده است", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index 7cc44651e568..53f1c0cb4dc1 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -4143,7 +4143,6 @@ "Room_has_been_archived": "Huone on arkistoitu", "Room_has_been_converted": "Huone on muunnettu", "Room_has_been_created": "Huone on luotu", - "Room_has_been_deleted": "Huone on poistettu", "Room_has_been_removed": "Huone on poistettu", "Room_has_been_unarchived": "Huone on palautettu arkistosta", "Room_Info": "Huoneen tiedot", diff --git a/packages/i18n/src/locales/fr.i18n.json b/packages/i18n/src/locales/fr.i18n.json index b8c6ee5b6414..2bcbb3e78a62 100644 --- a/packages/i18n/src/locales/fr.i18n.json +++ b/packages/i18n/src/locales/fr.i18n.json @@ -3607,7 +3607,6 @@ "room_disallowed_reacting": "Salon non autorisé à réagir par {{user_by}}", "Room_Edit": "Modifier le salon", "Room_has_been_archived": "Le salon a été archivé", - "Room_has_been_deleted": "Le salon a été supprimé", "Room_has_been_removed": "Le salon a été supprimé", "Room_has_been_unarchived": "Le salon a été désarchivé", "Room_Info": "Information sur le salon", diff --git a/packages/i18n/src/locales/he.i18n.json b/packages/i18n/src/locales/he.i18n.json index 84da76d59421..1675e10b1d8f 100644 --- a/packages/i18n/src/locales/he.i18n.json +++ b/packages/i18n/src/locales/he.i18n.json @@ -1146,7 +1146,6 @@ "Room_archived": "חדר בארכיון", "room_changed_privacy": "סוג החדר השנה ל:{{room_type}} ע\"י {{user_by}}", "room_changed_topic": "נושא החדר שונה ל:{{room_topic}} ע\"י {{user_by}}", - "Room_has_been_deleted": "חדר נמחק", "Room_Info": "פרטי החדר", "Room_name_changed": "שם החדר שונה ל: {{room_name}} על ידי המשתמש {{user_by}}", "Room_name_changed_successfully": "שם החדר שונה בהצלחה", diff --git a/packages/i18n/src/locales/hr.i18n.json b/packages/i18n/src/locales/hr.i18n.json index 5f3f16e6ed67..e88c450fc6ce 100644 --- a/packages/i18n/src/locales/hr.i18n.json +++ b/packages/i18n/src/locales/hr.i18n.json @@ -2235,7 +2235,6 @@ "Room_default_change_to_private_will_be_default_no_more": "To je zadani kanal i mijenja se u privatnu grupu, jer više neće biti zadani kanal. Želiš li nastaviti?", "Room_description_changed_successfully": "Opis sobe je uspješno promjenjen", "Room_has_been_archived": "Soba je arhivirana", - "Room_has_been_deleted": "Soba je obrisana", "Room_has_been_unarchived": "Soba je vraćena iz arhive", "Room_Info": "Info Sobe", "room_is_blocked": "Ova soba je blokirana", diff --git a/packages/i18n/src/locales/hu.i18n.json b/packages/i18n/src/locales/hu.i18n.json index 69afdb75f233..d352ad6492a3 100644 --- a/packages/i18n/src/locales/hu.i18n.json +++ b/packages/i18n/src/locales/hu.i18n.json @@ -3983,7 +3983,6 @@ "Room_Edit": "Szoba szerkesztése", "Room_has_been_archived": "A szoba archiválva lett", "Room_has_been_created": "A szoba létre lett hozva", - "Room_has_been_deleted": "A szoba törölve lett", "Room_has_been_removed": "A szoba el lett távolítva", "Room_has_been_unarchived": "A szoba archiválása meg lett szüntetve", "Room_Info": "Szobainformációk", diff --git a/packages/i18n/src/locales/id.i18n.json b/packages/i18n/src/locales/id.i18n.json index db105e17538e..e0656e017425 100644 --- a/packages/i18n/src/locales/id.i18n.json +++ b/packages/i18n/src/locales/id.i18n.json @@ -2110,7 +2110,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ini adalah saluran default dan mengubahnya menjadi grup pribadi akan menyebabkannya menjadi saluran default. Apakah kamu ingin melanjutkan?", "Room_description_changed_successfully": "Deskripsi kamar berubah dengan sukses", "Room_has_been_archived": "Kamar telah diarsipkan", - "Room_has_been_deleted": "Kamar telah dihapus", "Room_has_been_unarchived": "Kamar telah diarsipkan", "Room_Info": "Info kamar", "room_is_blocked": "Ruangan ini diblokir", diff --git a/packages/i18n/src/locales/it.i18n.json b/packages/i18n/src/locales/it.i18n.json index 79dcaded9d78..315780095cd9 100644 --- a/packages/i18n/src/locales/it.i18n.json +++ b/packages/i18n/src/locales/it.i18n.json @@ -2622,7 +2622,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Questo è un canale predefinito e cambiandolo a un gruppo privato non causerà più un canale predefinito. Vuoi procedere?", "Room_description_changed_successfully": "Descrizione del canale cambiata con successo", "Room_has_been_archived": "La stanza è stata archiviata", - "Room_has_been_deleted": "Il canale è stato eliminato", "Room_has_been_unarchived": "La stanza è stata tolta dall'archivio", "Room_Info": "Informazioni canale", "room_is_blocked": "La stanza è sbloccata", diff --git a/packages/i18n/src/locales/ja.i18n.json b/packages/i18n/src/locales/ja.i18n.json index 2e50c926f7bb..27a99598d88d 100644 --- a/packages/i18n/src/locales/ja.i18n.json +++ b/packages/i18n/src/locales/ja.i18n.json @@ -3568,7 +3568,6 @@ "room_disallowed_reacting": "Roomで{{user_by}}による応答が禁止されました", "Room_Edit": "Roomの編集", "Room_has_been_archived": "Roomはアーカイブされました", - "Room_has_been_deleted": "Roomが削除されました", "Room_has_been_removed": "Roomが削除されました", "Room_has_been_unarchived": "Roomのアーカイブが解除されました", "Room_Info": "Room情報", diff --git a/packages/i18n/src/locales/ka-GE.i18n.json b/packages/i18n/src/locales/ka-GE.i18n.json index 7fab42bd2f14..7e28a9792559 100644 --- a/packages/i18n/src/locales/ka-GE.i18n.json +++ b/packages/i18n/src/locales/ka-GE.i18n.json @@ -2816,7 +2816,6 @@ "Room_default_change_to_private_will_be_default_no_more": "ეს არის დეფაულტ არხი და პირად ჯგუფად გადაკეთების შემთხვევაში აღარ იქნება დეფაულტ არხი.გსურთ გაგრძელება?", "Room_description_changed_successfully": "ოთახის აღწერა წარმატებით შეიცვალა", "Room_has_been_archived": "ოთახი დაარქივებულია", - "Room_has_been_deleted": "ოთახი წაიშალა", "Room_has_been_unarchived": "ოთახი ამოარქივდა", "Room_Info": "ოთახის ინფორმაცია", "room_is_blocked": "ოთახი დაბლოკილია", diff --git a/packages/i18n/src/locales/km.i18n.json b/packages/i18n/src/locales/km.i18n.json index 116fbfc12d6a..f995ba297bd0 100644 --- a/packages/i18n/src/locales/km.i18n.json +++ b/packages/i18n/src/locales/km.i18n.json @@ -2420,7 +2420,6 @@ "Room_default_change_to_private_will_be_default_no_more": "នេះជាឆានែលលំនាំដើមហើយការប្តូរវាទៅជាក្រុមឯកជននឹងធ្វើឱ្យវាលែងក្លាយជាឆានែលលំនាំដើម។ តើអ្នកចង់បន្តទេ?", "Room_description_changed_successfully": "ការពិពណ៌នាបន្ទប់បានផ្លាស់ប្ដូរដោយជោគជ័យ", "Room_has_been_archived": "បន្ទប់ត្រូវបានទុកក្នុងប័ណ្ណសារ", - "Room_has_been_deleted": "បន្ទប់ត្រូវបានលុបចោល", "Room_has_been_unarchived": "បន្ទប់មិនត្រូវបានទុកក្នុងប័ណ្ណសារ", "Room_Info": "ព័តមានបន្ទប់", "room_is_blocked": "បន្ទប់នេះត្រូវបានទប់ស្កាត់", diff --git a/packages/i18n/src/locales/ko.i18n.json b/packages/i18n/src/locales/ko.i18n.json index 468da203f883..7f1fae38eda4 100644 --- a/packages/i18n/src/locales/ko.i18n.json +++ b/packages/i18n/src/locales/ko.i18n.json @@ -3077,7 +3077,6 @@ "Room_default_change_to_private_will_be_default_no_more": "이 채널은 기본 채널이며 비공개 그룹으로 변경하면 더 이상 기본 채널이 될 수 없습니다. 진행하시겠습니까?", "Room_description_changed_successfully": "대화방 설명을 변경했습니다.", "Room_has_been_archived": "대화방이 보관되었습니다.", - "Room_has_been_deleted": "대화방이 삭제되었습니다.", "Room_has_been_unarchived": "대화방 보관이 해제되었습니다.", "Room_Info": "대화방 정보", "room_is_blocked": "이 대화방은 차단되었습니다.", diff --git a/packages/i18n/src/locales/ku.i18n.json b/packages/i18n/src/locales/ku.i18n.json index b5b69caa0143..ccf008f20b78 100644 --- a/packages/i18n/src/locales/ku.i18n.json +++ b/packages/i18n/src/locales/ku.i18n.json @@ -2096,7 +2096,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ev kanalek navekî ye û guhartin bi komê taybet re dê bibe sedema kanalek navekî. Ma hûn dixwazin pêşniyar bikin?", "Room_description_changed_successfully": "Pirtûka jor bi serkeftî guhertin", "Room_has_been_archived": "Room tête arşîvkirin", - "Room_has_been_deleted": "Room hatiye jêbirin", "Room_has_been_unarchived": "Room unarchived", "Room_Info": "Info room", "room_is_blocked": "Ev odeyê hate asteng kirin", diff --git a/packages/i18n/src/locales/lo.i18n.json b/packages/i18n/src/locales/lo.i18n.json index 7bcd89dc402a..ce86290fe61e 100644 --- a/packages/i18n/src/locales/lo.i18n.json +++ b/packages/i18n/src/locales/lo.i18n.json @@ -2140,7 +2140,6 @@ "Room_default_change_to_private_will_be_default_no_more": "ນີ້ແມ່ນຊ່ອງທາງເລີ່ມຕົ້ນແລະການປ່ຽນແປງມັນກັບກຸ່ມເອກະຊົນຈະເຮັດໃຫ້ມັນບໍ່ກາຍເປັນຊ່ອງທາງເລີ່ມຕົ້ນ. ທ່ານຕ້ອງການດໍາເນີນການ?", "Room_description_changed_successfully": "ລາຍະການຫ້ອງປ່ຽນແປງຢ່າງລວດໄວ", "Room_has_been_archived": "ຫ້ອງພັກໄດ້ຖືກເກັບໄວ້", - "Room_has_been_deleted": "ຫ້ອງໄດ້ຖືກລຶບອອກແລ້ວ", "Room_has_been_unarchived": "ຫ້ອງໄດ້ຖືກເປີດເຜີຍ", "Room_Info": "ຂໍ້ມູນຫ້ອງ", "room_is_blocked": "ຫ້ອງນີ້ຖືກປິດ", diff --git a/packages/i18n/src/locales/lt.i18n.json b/packages/i18n/src/locales/lt.i18n.json index 3a866a790e77..350049212f85 100644 --- a/packages/i18n/src/locales/lt.i18n.json +++ b/packages/i18n/src/locales/lt.i18n.json @@ -2157,7 +2157,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Tai numatytasis kanalas ir pakeičia jį į privačią grupę, todėl jis nebebus numatytasis kanalas. Ar norite testi?", "Room_description_changed_successfully": "Kambario aprašymas sėkmingai pakeistas", "Room_has_been_archived": "Kambarys buvo archyvuojamas", - "Room_has_been_deleted": "Kambarys buvo ištrintas", "Room_has_been_unarchived": "Kambarys buvo unarchyvuotas", "Room_Info": "Kambarių informacija", "room_is_blocked": "Šis kambarys yra užblokuotas", diff --git a/packages/i18n/src/locales/lv.i18n.json b/packages/i18n/src/locales/lv.i18n.json index 697c5b577600..f74ff4a62af2 100644 --- a/packages/i18n/src/locales/lv.i18n.json +++ b/packages/i18n/src/locales/lv.i18n.json @@ -2113,7 +2113,6 @@ "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", - "Room_has_been_deleted": "Istaba ir dzēsta", "Room_has_been_unarchived": "Istaba ir izņemta no arhīva", "Room_Info": "Istabas informācija", "room_is_blocked": "Šī istaba ir bloķēta", diff --git a/packages/i18n/src/locales/mn.i18n.json b/packages/i18n/src/locales/mn.i18n.json index b8dd78554925..df3463de92a9 100644 --- a/packages/i18n/src/locales/mn.i18n.json +++ b/packages/i18n/src/locales/mn.i18n.json @@ -2097,7 +2097,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Энэ нь анхдагч суваг бөгөөд үүнийг хувийн бүлэгт өөрчилснөөр энэ нь анхдагч суваг байхаа больсон. Та үргэлжлүүлэхийг хүсч байна уу?", "Room_description_changed_successfully": "Өрөөний тодорхойлолт амжилттай болсон", "Room_has_been_archived": "Өрөө архивлагдсан байна", - "Room_has_been_deleted": "Өрөө устгагдсан байна", "Room_has_been_unarchived": "Өрөө нь албан ёсоор бүртгэгдээгүй байна", "Room_Info": "Өрөөний мэдээлэл", "room_is_blocked": "Энэ өрөө хаагдсан", diff --git a/packages/i18n/src/locales/ms-MY.i18n.json b/packages/i18n/src/locales/ms-MY.i18n.json index 4089904315cb..065170e2ee3b 100644 --- a/packages/i18n/src/locales/ms-MY.i18n.json +++ b/packages/i18n/src/locales/ms-MY.i18n.json @@ -2109,7 +2109,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ini adalah saluran lalai dan mengubahnya ke kumpulan persendirian akan menyebabkan ia tidak lagi menjadi saluran lalai. Adakah anda mahu meneruskan?", "Room_description_changed_successfully": "Penerangan bilik berubah dengan jayanya", "Room_has_been_archived": "Bilik telah diarkibkan", - "Room_has_been_deleted": "Bilik telah dipadam", "Room_has_been_unarchived": "Bilik telah dirahsiakan", "Room_Info": "Maklumat bilik", "room_is_blocked": "Bilik ini disekat", diff --git a/packages/i18n/src/locales/nl.i18n.json b/packages/i18n/src/locales/nl.i18n.json index a5626058a8fe..93433dff0bee 100644 --- a/packages/i18n/src/locales/nl.i18n.json +++ b/packages/i18n/src/locales/nl.i18n.json @@ -3597,7 +3597,6 @@ "room_disallowed_reacting": "Reageren in kamer mag niet meer door {{user_by}}", "Room_Edit": "Kamer bewerken", "Room_has_been_archived": "Kamer is gearchiveerd", - "Room_has_been_deleted": "Kamer is verwijderd", "Room_has_been_removed": "Kamer werd verwijderd", "Room_has_been_unarchived": "Kamer is uit archief gehaald", "Room_Info": "Kamerinformatie", diff --git a/packages/i18n/src/locales/no.i18n.json b/packages/i18n/src/locales/no.i18n.json index 0397bf518f7c..769e58c1e6a8 100644 --- a/packages/i18n/src/locales/no.i18n.json +++ b/packages/i18n/src/locales/no.i18n.json @@ -3513,7 +3513,6 @@ "Room_has_been_archived": "Rom har blitt arkivert", "Room_has_been_converted": "Room er konvertert", "Room_has_been_created": "Room er opprettet", - "Room_has_been_deleted": "Rommet har blitt slettet", "Room_has_been_removed": "Room er fjernet", "Room_has_been_unarchived": "Rom har blitt arkivert", "Room_Info": "Rominformasjon", diff --git a/packages/i18n/src/locales/pl.i18n.json b/packages/i18n/src/locales/pl.i18n.json index 0d2325e2e47e..e6d04c3ae9c7 100644 --- a/packages/i18n/src/locales/pl.i18n.json +++ b/packages/i18n/src/locales/pl.i18n.json @@ -3926,7 +3926,6 @@ "Room_Edit": "Edycja Room", "Room_has_been_archived": "Pokój został zarchiwizowany", "Room_has_been_created": "Room został utworzony", - "Room_has_been_deleted": "Pokój został usunięty", "Room_has_been_removed": "Room został usunięty", "Room_has_been_unarchived": "Pokój został przywrócony", "Room_Info": "Ustawienia pokoju", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 4ae11215c126..5849d51cc5bb 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -3697,7 +3697,6 @@ "room_disallowed_reacting": "Permissão de reagir removida da sala por {{user_by}}", "Room_Edit": "Editar Sala", "Room_has_been_archived": "A sala foi arquivada", - "Room_has_been_deleted": "A sala foi excluída", "Room_has_been_removed": "Sala foi removida", "Room_has_been_unarchived": "A sala foi desarquivada", "Room_Info": "Informações da Sala", diff --git a/packages/i18n/src/locales/pt.i18n.json b/packages/i18n/src/locales/pt.i18n.json index d60379ef7ed9..c3bf9d739b21 100644 --- a/packages/i18n/src/locales/pt.i18n.json +++ b/packages/i18n/src/locales/pt.i18n.json @@ -2442,7 +2442,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Este é um canal padrão e mudá-lo para um grupo privado fará com que ele não seja mais um canal padrão. Você quer prosseguir?", "Room_description_changed_successfully": "A descrição da sala mudou com sucesso", "Room_has_been_archived": "A sala foi arquivada", - "Room_has_been_deleted": "A sala foi removida", "Room_has_been_unarchived": "A sala foi desarquivada", "Room_Info": "Informações da sala", "room_is_blocked": "Esta sala está bloqueada", diff --git a/packages/i18n/src/locales/ro.i18n.json b/packages/i18n/src/locales/ro.i18n.json index 21e950c87678..8f6974fde123 100644 --- a/packages/i18n/src/locales/ro.i18n.json +++ b/packages/i18n/src/locales/ro.i18n.json @@ -2101,7 +2101,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Acesta este un canal implicit și schimbarea acestuia într-un grup privat va determina ca acesta să nu mai fie un canal implicit. Doriți să continuați?", "Room_description_changed_successfully": "Descrierea camerei sa schimbat cu succes", "Room_has_been_archived": "Camera a fost arhivată", - "Room_has_been_deleted": "Camera a fost ștearsă", "Room_has_been_unarchived": "Camera a fost dezarhivată", "Room_Info": "Info cameră", "room_is_blocked": "Această cameră este blocată", diff --git a/packages/i18n/src/locales/ru.i18n.json b/packages/i18n/src/locales/ru.i18n.json index b014b6da9d56..affb21c147a0 100644 --- a/packages/i18n/src/locales/ru.i18n.json +++ b/packages/i18n/src/locales/ru.i18n.json @@ -3772,7 +3772,6 @@ "room_disallowed_reacting": "Пользователь {{user_by}} запретил реакции в комнате", "Room_Edit": "Редактировать чат", "Room_has_been_archived": "Комната была архивирована", - "Room_has_been_deleted": "Комната была удалена", "Room_has_been_removed": "Комната удалена", "Room_has_been_unarchived": "Канал был восстановлен", "Room_Info": "Информация о чате", diff --git a/packages/i18n/src/locales/sk-SK.i18n.json b/packages/i18n/src/locales/sk-SK.i18n.json index 8e80bd7a0aff..27100c086c67 100644 --- a/packages/i18n/src/locales/sk-SK.i18n.json +++ b/packages/i18n/src/locales/sk-SK.i18n.json @@ -2111,7 +2111,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Toto je predvolený kanál a jeho zmena na súkromnú skupinu spôsobí, že už nebude predvoleným kanálom. Chcete pokračovať?", "Room_description_changed_successfully": "Popis izby sa úspešne zmenil", "Room_has_been_archived": "Izba bola archivovaná", - "Room_has_been_deleted": "Miestnosť bola vymazaná", "Room_has_been_unarchived": "Izba bola zrušená", "Room_Info": "Informácie o izbe", "room_is_blocked": "Táto miestnosť je zablokovaná", diff --git a/packages/i18n/src/locales/sl-SI.i18n.json b/packages/i18n/src/locales/sl-SI.i18n.json index d08c083ee9ce..a3630f6ce6c6 100644 --- a/packages/i18n/src/locales/sl-SI.i18n.json +++ b/packages/i18n/src/locales/sl-SI.i18n.json @@ -2091,7 +2091,6 @@ "Room_default_change_to_private_will_be_default_no_more": "To je privzeti kanal. Če ga spremenite v zasebno skupino, ne bo več privzeti kanal. Želite nadaljevati?", "Room_description_changed_successfully": "Opis sobe je uspešno spremenjen", "Room_has_been_archived": "Soba je bila arhivirana", - "Room_has_been_deleted": "Soba je bila izbrisana", "Room_has_been_unarchived": "Soba je bila odarhivirana", "Room_Info": "Informacije o sobi", "room_is_blocked": "Ta soba je blokirana", diff --git a/packages/i18n/src/locales/sq.i18n.json b/packages/i18n/src/locales/sq.i18n.json index 80ff384d4cc4..73e0ca0a56ed 100644 --- a/packages/i18n/src/locales/sq.i18n.json +++ b/packages/i18n/src/locales/sq.i18n.json @@ -2101,7 +2101,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ky është një kanal i parazgjedhur dhe ndryshimi i tij në një grup privat do të bëjë që ajo të mos jetë më një kanal i paracaktuar. A doni të vazhdoni?", "Room_description_changed_successfully": "Përshkrimi i dhomës u ndryshua me sukses", "Room_has_been_archived": "Dhoma është arkivuar", - "Room_has_been_deleted": "Dhoma është fshirë", "Room_has_been_unarchived": "Dhoma nuk është arkivuar", "Room_Info": "Room Info", "room_is_blocked": "Kjo dhomë është e bllokuar", diff --git a/packages/i18n/src/locales/sr.i18n.json b/packages/i18n/src/locales/sr.i18n.json index adec89c9568b..4a741a750d4f 100644 --- a/packages/i18n/src/locales/sr.i18n.json +++ b/packages/i18n/src/locales/sr.i18n.json @@ -1930,7 +1930,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Ово је подразумевани канал, а промена у приватну групу ће више не бити подразумевани канал. Да ли желите да наставите?", "Room_description_changed_successfully": "Опис собе се успешно промјенио", "Room_has_been_archived": "Соба је архивирана", - "Room_has_been_deleted": "Соба је избрисана", "Room_has_been_unarchived": "Соба је деархивирана", "Room_Info": "Информације о соби", "room_is_blocked": "Ова просторија је блокирана", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index f8eb0c610896..56cc49003941 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -4149,7 +4149,6 @@ "Room_has_been_archived": "Rummet har arkiverats", "Room_has_been_converted": "Rum har konverterats", "Room_has_been_created": "Rum har skapats", - "Room_has_been_deleted": "Rummet har raderats", "Room_has_been_removed": "Rummet har tagits bort", "Room_has_been_unarchived": "Rummet har tagits bort från arkivet", "Room_Info": "Rumsinformation", diff --git a/packages/i18n/src/locales/ta-IN.i18n.json b/packages/i18n/src/locales/ta-IN.i18n.json index 4ee378f67319..7ae4d9a96653 100644 --- a/packages/i18n/src/locales/ta-IN.i18n.json +++ b/packages/i18n/src/locales/ta-IN.i18n.json @@ -2102,7 +2102,6 @@ "Room_default_change_to_private_will_be_default_no_more": "இது ஒரு இயல்புநிலை சேனலாகும், மேலும் அது ஒரு தனியார் குழுவாக மாற்றப்படுவதால் இனி இயல்புநிலை சேனலாக இருக்காது. நீங்கள் தொடர விரும்புகிறீர்களா?", "Room_description_changed_successfully": "அறை விளக்கம் வெற்றிகரமாக மாற்றப்பட்டது", "Room_has_been_archived": "அறை காப்பகப்படுத்தப்பட்டுள்ளது", - "Room_has_been_deleted": "அறை நீக்கப்பட்டுள்ளது", "Room_has_been_unarchived": "அறை அகற்றப்பட்டது", "Room_Info": "அறை தகவல்", "room_is_blocked": "இந்த அறை தடுக்கப்பட்டுள்ளது", diff --git a/packages/i18n/src/locales/th-TH.i18n.json b/packages/i18n/src/locales/th-TH.i18n.json index 996333840758..e9f3705b559f 100644 --- a/packages/i18n/src/locales/th-TH.i18n.json +++ b/packages/i18n/src/locales/th-TH.i18n.json @@ -2095,7 +2095,6 @@ "Room_default_change_to_private_will_be_default_no_more": "นี่เป็นช่องทางเริ่มต้นและเปลี่ยนเป็นกลุ่มส่วนตัวจะทำให้ช่องนี้ไม่ได้เป็นช่องเริ่มต้นอีกต่อไป คุณต้องการดำเนินการต่อหรือไม่?", "Room_description_changed_successfully": "เปลี่ยนรายละเอียดห้องเรียบร้อยแล้ว", "Room_has_been_archived": "ห้องพักได้รับการเก็บถาวรแล้ว", - "Room_has_been_deleted": "ห้องถูกลบแล้ว", "Room_has_been_unarchived": "ห้องพักได้รับการยกเลิกการเก็บถาวรแล้ว", "Room_Info": "ข้อมูลห้องพัก", "room_is_blocked": "ห้องนี้ถูกบล็อก", diff --git a/packages/i18n/src/locales/tr.i18n.json b/packages/i18n/src/locales/tr.i18n.json index c4899dab439e..fbfe4c1a1d8c 100644 --- a/packages/i18n/src/locales/tr.i18n.json +++ b/packages/i18n/src/locales/tr.i18n.json @@ -2506,7 +2506,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Bu varsayılan bir kanaldır ve özel bir grup olarak değiştirmek, artık varsayılan kanal olmamasına neden olacaktır. Devam etmek istiyor musunuz?", "Room_description_changed_successfully": "Oda açıklaması başarıyla değiştirildi", "Room_has_been_archived": "Oda arşivlendi", - "Room_has_been_deleted": "Oda silindi", "Room_has_been_unarchived": "Oda arşivden çıkarıldı", "Room_Info": "Oda Bilgileri", "room_is_blocked": "Bu oda engellendi", diff --git a/packages/i18n/src/locales/ug.i18n.json b/packages/i18n/src/locales/ug.i18n.json index ba8077844663..78c3650e0038 100644 --- a/packages/i18n/src/locales/ug.i18n.json +++ b/packages/i18n/src/locales/ug.i18n.json @@ -903,7 +903,6 @@ "Room_archived": "ئۆي ئاللىبۇرۇن تۈرگە ئايرىپ ساقلاندى", "room_changed_privacy": "{{room_type}}ئۆينىڭ تىپىنى بۇنداق ئۆزگەرتتى :{{user_by}}", "room_changed_topic": "{{room_topic}} ئۆينىڭ تېمىسىنى بۇنداق قىلىپ ئۆزگەرتتى:{{user_by}}", - "Room_has_been_deleted": "ئۆي ئاللىبۇرۇن يۇيۇلدى", "Room_Info": "ئۆينىڭ ئۇچۇرى", "Room_name_changed": "تىن ئۆزگەرتىلدى. {{user_by}} ،{{room_name}} پاراڭلىشىش ئۆينىڭ ئىسمى بۇنداق ئۆزگەرتىلدى", "Room_name_changed_successfully": "پاراڭلىشىش ئۆيىنىڭ ئىسمى ئۇتۇقلۇق ئۆزگەرتىلدى", diff --git a/packages/i18n/src/locales/uk.i18n.json b/packages/i18n/src/locales/uk.i18n.json index 94d470171761..0ed157a736c3 100644 --- a/packages/i18n/src/locales/uk.i18n.json +++ b/packages/i18n/src/locales/uk.i18n.json @@ -2639,7 +2639,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Це типовий канал і зміна його на приватну групу призведе до того, що він більше не буде каналом за умовчанням. Ви хочете продовжити?", "Room_description_changed_successfully": "Опис номера змінено успішно", "Room_has_been_archived": "Кімната була заархівована", - "Room_has_been_deleted": "Номер був видалений", "Room_has_been_unarchived": "Номер було неархівовано", "Room_Info": "опис кімнати", "room_is_blocked": "Цей номер заблоковано", diff --git a/packages/i18n/src/locales/vi-VN.i18n.json b/packages/i18n/src/locales/vi-VN.i18n.json index a2fc50733b1e..21fc37f11f01 100644 --- a/packages/i18n/src/locales/vi-VN.i18n.json +++ b/packages/i18n/src/locales/vi-VN.i18n.json @@ -2201,7 +2201,6 @@ "Room_default_change_to_private_will_be_default_no_more": "Đây là kênh mặc định và thay đổi kênh đó thành một nhóm riêng sẽ khiến kênh đó không còn là kênh mặc định. Bạn có muốn tiếp tục?", "Room_description_changed_successfully": "Mô tả phòng đã được thay đổi thành công", "Room_has_been_archived": "Phòng đã được lưu trữ", - "Room_has_been_deleted": "Phòng đã bị xóa", "Room_has_been_unarchived": "Phòng chưa được lưu trữ", "Room_Info": "Thông tin phòng", "room_is_blocked": "Phòng này bị chặn", diff --git a/packages/i18n/src/locales/zh-HK.i18n.json b/packages/i18n/src/locales/zh-HK.i18n.json index 2f416acc4f2f..fa44a7fed369 100644 --- a/packages/i18n/src/locales/zh-HK.i18n.json +++ b/packages/i18n/src/locales/zh-HK.i18n.json @@ -2123,7 +2123,6 @@ "Room_default_change_to_private_will_be_default_no_more": "这是一个默认频道,并将其更改为专用群组将使其不再是默认频道。你想继续吗?", "Room_description_changed_successfully": "房间描述已成功更改", "Room_has_been_archived": "房间已存档", - "Room_has_been_deleted": "房间已被删除", "Room_has_been_unarchived": "房间已被取消存档", "Room_Info": "客房信息", "room_is_blocked": "这个房间被封锁了", diff --git a/packages/i18n/src/locales/zh-TW.i18n.json b/packages/i18n/src/locales/zh-TW.i18n.json index 1b9cbed4a5ee..d15e7739f226 100644 --- a/packages/i18n/src/locales/zh-TW.i18n.json +++ b/packages/i18n/src/locales/zh-TW.i18n.json @@ -3478,7 +3478,6 @@ "Room_default_change_to_private_will_be_default_no_more": "這是一個預設頻道,並將其更改為專用群組將使其不再是預設頻道。你想繼續嗎?", "Room_description_changed_successfully": "Room 描述已成功更改", "Room_has_been_archived": "Room 已封存", - "Room_has_been_deleted": "Room 已經被刪除", "Room_has_been_unarchived": "Room 已被取消封存", "Room_Info": "Room 資訊", "room_is_blocked": "這個房間被封鎖了", diff --git a/packages/i18n/src/locales/zh.i18n.json b/packages/i18n/src/locales/zh.i18n.json index 6929e3eeab8d..b5a972637d81 100644 --- a/packages/i18n/src/locales/zh.i18n.json +++ b/packages/i18n/src/locales/zh.i18n.json @@ -3150,7 +3150,6 @@ "Room_default_change_to_private_will_be_default_no_more": "将默认频道更改为私人组将使其不再是默认频道。您想继续吗?", "Room_description_changed_successfully": "聊天室描述修改成功", "Room_has_been_archived": "聊天室已归档", - "Room_has_been_deleted": "聊天室已被删除", "Room_has_been_unarchived": "已撤销Room归档", "Room_Info": "聊天室信息", "room_is_blocked": "这个聊天室已被屏蔽", From b615b24ace95dc10d011bbde13b249220fba687d Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:00:58 -0300 Subject: [PATCH 07/16] chore: changes to SAML login in anticipation of meteor 3 (#32134) --- .../client/views/root/SAMLLoginRoute.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/root/SAMLLoginRoute.tsx b/apps/meteor/client/views/root/SAMLLoginRoute.tsx index 9b98db663469..5bf205c1fa2a 100644 --- a/apps/meteor/client/views/root/SAMLLoginRoute.tsx +++ b/apps/meteor/client/views/root/SAMLLoginRoute.tsx @@ -1,4 +1,4 @@ -import { useRouter, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useRouter, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { useEffect } from 'react'; @@ -12,16 +12,23 @@ const SAMLLoginRoute = () => { if (error) { dispatchToastMessage({ type: 'error', message: error }); } - - router.navigate( - { - pathname: '/home', - }, - { replace: true }, - ); }); }, [dispatchToastMessage, router]); + const userId = useUserId(); + useEffect(() => { + if (!userId) { + return; + } + + router.navigate( + { + pathname: '/home', + }, + { replace: true }, + ); + }, [userId, router]); + return null; }; From ec544d6803c328ff316de323eefdc23776c0a9e9 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 4 Apr 2024 19:35:19 -0300 Subject: [PATCH 08/16] fix: livechat room desync on different windows (#32135) --- .changeset/wild-keys-obey.md | 5 +++++ packages/livechat/src/lib/room.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/wild-keys-obey.md diff --git a/.changeset/wild-keys-obey.md b/.changeset/wild-keys-obey.md new file mode 100644 index 000000000000..9de92ee5671b --- /dev/null +++ b/.changeset/wild-keys-obey.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/livechat": patch +--- + +Fixes issue causing a desync in different browser windows when a chat is closed and started again diff --git a/packages/livechat/src/lib/room.js b/packages/livechat/src/lib/room.js index 20d86d22a5cc..2dc6a93074b2 100644 --- a/packages/livechat/src/lib/room.js +++ b/packages/livechat/src/lib/room.js @@ -348,7 +348,7 @@ export const defaultRoomParams = () => { store.on('change', ([state, prevState]) => { // Cross-tab communication // Detects when a room is created and then route to the correct container - if (!prevState.room && state.room) { + if (prevState.room?._id !== state.room?._id) { route('/'); } }); From e5bbf83adf12bec7d7962294cfe980955a77f92b Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 5 Apr 2024 10:51:44 -0300 Subject: [PATCH 09/16] fix: Missing space between name and user name on system messages (#32136) --- .changeset/smart-squids-begin.md | 5 +++++ .../client/components/message/variants/SystemMessage.tsx | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/smart-squids-begin.md diff --git a/.changeset/smart-squids-begin.md b/.changeset/smart-squids-begin.md new file mode 100644 index 000000000000..48f3f460ea7e --- /dev/null +++ b/.changeset/smart-squids-begin.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes the missing space between name and user name on system messages diff --git a/apps/meteor/client/components/message/variants/SystemMessage.tsx b/apps/meteor/client/components/message/variants/SystemMessage.tsx index d69b1ada10dc..9b1a82a156eb 100644 --- a/apps/meteor/client/components/message/variants/SystemMessage.tsx +++ b/apps/meteor/client/components/message/variants/SystemMessage.tsx @@ -85,7 +85,12 @@ const SystemMessage = ({ message, showUserAvatar, ...props }: SystemMessageProps {...triggerProps} > {getUserDisplayName(user.name, user.username, showRealName)} - {showUsername && @{user.username}} + {showUsername && ( + <> + {' '} + @{user.username} + + )} {messageType && ( Date: Fri, 5 Apr 2024 10:58:12 -0300 Subject: [PATCH 10/16] feat: `Contextualbar` resizable (#29461) Co-authored-by: Guilherme Gazzo --- .changeset/good-ghosts-doubt.md | 5 +++ .../Contextualbar/ContextualbarDialog.tsx | 13 +++++- .../Contextualbar/ContextualbarResizable.tsx | 40 +++++++++++++++++++ .../directory/CallsContextualBarDirectory.tsx | 6 +-- .../VideoConfList/VideoConfListItem.tsx | 2 +- apps/meteor/package.json | 1 + packages/i18n/src/locales/en.i18n.json | 2 + .../src/hooks/useFeaturePreviewList.ts | 10 ++++- yarn.lock | 11 +++++ 9 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 .changeset/good-ghosts-doubt.md create mode 100644 apps/meteor/client/components/Contextualbar/ContextualbarResizable.tsx diff --git a/.changeset/good-ghosts-doubt.md b/.changeset/good-ghosts-doubt.md new file mode 100644 index 000000000000..5f4ed8f5a36d --- /dev/null +++ b/.changeset/good-ghosts-doubt.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Introduces a resizable Contextualbar allowing users to change the width just by dragging it diff --git a/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx index 087149bd4a4e..c3421f3fc9d3 100644 --- a/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx +++ b/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx @@ -1,4 +1,5 @@ import { Contextualbar } from '@rocket.chat/fuselage'; +import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import { useLayoutSizes, useLayoutContextualBarPosition } from '@rocket.chat/ui-contexts'; import type { ComponentProps, KeyboardEvent } from 'react'; import React, { useCallback, useRef } from 'react'; @@ -6,6 +7,7 @@ import type { AriaDialogProps } from 'react-aria'; import { FocusScope, useDialog } from 'react-aria'; import { useRoomToolbox } from '../../views/room/contexts/RoomToolboxContext'; +import ContextualbarResizable from './ContextualbarResizable'; type ContextualbarDialogProps = AriaDialogProps & ComponentProps; @@ -38,7 +40,16 @@ const ContextualbarDialog = (props: ContextualbarDialogProps) => { return ( - + + + + + + + + + + ); }; diff --git a/apps/meteor/client/components/Contextualbar/ContextualbarResizable.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarResizable.tsx new file mode 100644 index 000000000000..05dd9bf0cf2e --- /dev/null +++ b/apps/meteor/client/components/Contextualbar/ContextualbarResizable.tsx @@ -0,0 +1,40 @@ +import { css } from '@rocket.chat/css-in-js'; +import { Palette, Box } from '@rocket.chat/fuselage'; +import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { Resizable } from 're-resizable'; +import type { ComponentProps } from 'react'; +import React from 'react'; + +type ContextualbarResizableProps = { defaultWidth: string } & ComponentProps; + +const ContextualbarResizable = ({ defaultWidth, children, ...props }: ContextualbarResizableProps) => { + const [contextualbarWidth, setContextualbarWidth] = useLocalStorage('contextualbarWidth', defaultWidth); + const handleStyle = css` + height: 100%; + &:hover { + background-color: ${Palette.stroke['stroke-highlight']}; + } + `; + + return ( + { + setContextualbarWidth(elRef.style.width); + }} + defaultSize={{ + width: contextualbarWidth, + height: '100%', + }} + minWidth={defaultWidth} + maxWidth='50%' + minHeight='100%' + handleStyles={{ left: { width: '3px', zIndex: '5', left: 0 } }} + handleComponent={{ left: }} + > + {children} + + ); +}; + +export default ContextualbarResizable; diff --git a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx index 010bb5f65517..bf35c50a6bf3 100644 --- a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx +++ b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx @@ -20,7 +20,7 @@ const CallsContextualBarDirectory: FC = () => { const t = useTranslation(); - const handleCallsContextualbarCloseButtonClick = (): void => { + const handleClose = (): void => { directoryRoute.push({ page: 'calls' }); }; @@ -52,9 +52,7 @@ const CallsContextualBarDirectory: FC = () => { const room = data.room as unknown as IVoipRoom; // TODO Check why types are incompatible even though the endpoint returns an IVoipRooms - return ( - {bar === 'info' && } - ); + return {bar === 'info' && }; }; export default CallsContextualBarDirectory; diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx index 0e155bc2b0b6..194380f2955e 100644 --- a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx +++ b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx @@ -54,7 +54,7 @@ const VideoConfListItem = ({ return ( feature.enabled); diff --git a/yarn.lock b/yarn.lock index ba550482d119..8993ef8f84cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9465,6 +9465,7 @@ __metadata: query-string: ^7.1.3 queue-fifo: ^0.2.6 rc-scrollbars: ^1.1.6 + re-resizable: ^6.9.9 react: ~17.0.2 react-aria: ~3.23.1 react-docgen-typescript-plugin: ^1.0.5 @@ -34700,6 +34701,16 @@ __metadata: languageName: node linkType: hard +"re-resizable@npm:^6.9.9": + version: 6.9.9 + resolution: "re-resizable@npm:6.9.9" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: a2c8bfe86646fb02d5c9c624b1da26f9e6a5e2f552cd96ce4db690588bee6b21177065ce8e98646c6ca0b1a9c4ce233824b75eb346800d8248ac8a87b40f1b28 + languageName: node + linkType: hard + "react-aria@npm:~3.23.1": version: 3.23.1 resolution: "react-aria@npm:3.23.1" From 11d65a546cfb8207874674ec6a8d9193cf8d7e64 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 5 Apr 2024 12:21:04 -0300 Subject: [PATCH 11/16] ci: fix services container not building in build step (#32075) --- .github/actions/build-docker/action.yml | 16 ++++++---------- .github/workflows/ci-test-e2e.yml | 8 ++++++++ .github/workflows/ci.yml | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 284a0985b78e..364957ecdf01 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -13,6 +13,10 @@ inputs: required: false description: 'Platform' type: string + build-containers: + required: false + description: 'Containers to build along with Rocket.Chat' + type: string runs: using: composite @@ -54,11 +58,7 @@ runs: - name: Build Docker images shell: bash run: | - args=(rocketchat) - - if [[ '${{ inputs.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + args=(rocketchat ${{ inputs.build-containers }}) docker compose -f docker-compose-ci.yml build "${args[@]}" @@ -66,10 +66,6 @@ runs: if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') shell: bash run: | - args=(rocketchat) - - if [[ '${{ inputs.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + args=(rocketchat ${{ inputs.build-containers }}) docker compose -f docker-compose-ci.yml push "${args[@]}" diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 1d953b9eb01f..12bb361d76f2 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -86,6 +86,14 @@ jobs: name: MongoDB ${{ matrix.mongodb-version }} (${{ matrix.shard }}/${{ inputs.total-shard }}) steps: + - name: Login to GitHub Container Registry + if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ secrets.CR_USER }} + password: ${{ secrets.CR_PAT }} + - name: Launch MongoDB uses: supercharge/mongodb-github-action@v1.10.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a78ca940d03..51e034505a85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,6 @@ jobs: RC_DOCKER_TAG: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-docker-tag-alpine || needs.release-versions.outputs.rc-docker-tag }} DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} - SERVICES_PUBLISH: 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service' strategy: fail-fast: false @@ -237,6 +236,7 @@ jobs: CR_PAT: ${{ secrets.CR_PAT }} node-version: ${{ needs.release-versions.outputs.node-version }} platform: ${{ matrix.platform }} + build-containers: ${{ matrix.platform == 'alpine' && 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service queue-worker-service omnichannel-transcript-service' || '' }} build-gh-docker: name: 🚢 Build Docker Images for Production @@ -248,7 +248,6 @@ jobs: RC_DOCKER_TAG: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-docker-tag-alpine || needs.release-versions.outputs.rc-docker-tag }} DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} - SERVICES_PUBLISH: 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service' strategy: fail-fast: false @@ -264,6 +263,7 @@ jobs: CR_PAT: ${{ secrets.CR_PAT }} node-version: ${{ needs.release-versions.outputs.node-version }} platform: ${{ matrix.platform }} + build-containers: ${{ matrix.platform == 'alpine' && 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service queue-worker-service omnichannel-transcript-service' || '' }} - name: Rename official Docker tag to GitHub Container Registry if: matrix.platform == 'official' From 4aba7c8a263e11ba04f8c82322fe493850b3912e Mon Sep 17 00:00:00 2001 From: Allan RIbeiro <35040806+AllanPazRibeiro@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:20:07 -0300 Subject: [PATCH 12/16] feat: allowing forward to offline dep (#31976) Co-authored-by: Guilherme Gazzo --- .changeset/strange-rivers-live.md | 8 + apps/meteor/app/livechat/server/lib/Helper.ts | 23 ++- .../app/livechat/server/lib/RoutingManager.ts | 8 +- .../livechat/server/methods/saveDepartment.ts | 1 + .../departments/EditDepartment.tsx | 14 ++ .../server/lib/LivechatEnterprise.ts | 1 + apps/meteor/tests/data/livechat/department.ts | 52 +++++-- apps/meteor/tests/data/livechat/users.ts | 20 ++- .../tests/end-to-end/api/livechat/00-rooms.ts | 138 +++++++++++++++++- .../end-to-end/api/livechat/10-departments.ts | 13 +- .../core-typings/src/ILivechatDepartment.ts | 2 + packages/i18n/src/locales/en.i18n.json | 2 + 12 files changed, 260 insertions(+), 22 deletions(-) create mode 100644 .changeset/strange-rivers-live.md diff --git a/.changeset/strange-rivers-live.md b/.changeset/strange-rivers-live.md new file mode 100644 index 000000000000..b1ebd05c284d --- /dev/null +++ b/.changeset/strange-rivers-live.md @@ -0,0 +1,8 @@ +--- +'@rocket.chat/core-typings': minor +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Added support for allowing agents to forward inquiries to departments that may not have any online agents given that `Allow department to receive forwarded inquiries even when there's no available agents` is set to `true` in the department configuration. +This configuration empowers agents to seamlessly direct incoming requests to the designated department, ensuring efficient handling of queries even when departmental resources are not actively online. When an agent becomes available, any pending inquiries will be automatically routed to them if the routing algorithm supports it. diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 3f9a555d6b86..3c1d601250c1 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -539,10 +539,24 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi agent = { agentId, username }; } - if (!RoutingManager.getConfig()?.autoAssignAgent || !(await Omnichannel.isWithinMACLimit(room))) { + const department = await LivechatDepartment.findOneById< + Pick + >(departmentId, { + projection: { + allowReceiveForwardOffline: 1, + fallbackForwardDepartment: 1, + name: 1, + }, + }); + + if ( + !RoutingManager.getConfig()?.autoAssignAgent || + !(await Omnichannel.isWithinMACLimit(room)) || + (department?.allowReceiveForwardOffline && !(await LivechatTyped.checkOnlineAgents(departmentId))) + ) { logger.debug(`Room ${room._id} will be on department queue`); await LivechatTyped.saveTransferHistory(room, transferData); - return RoutingManager.unassignAgent(inquiry, departmentId); + return RoutingManager.unassignAgent(inquiry, departmentId, true); } // Fake the department to forward the inquiry - Case the forward process does not success @@ -559,11 +573,6 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi const { servedBy, chatQueued } = roomTaken; if (!chatQueued && oldServedBy && servedBy && oldServedBy._id === servedBy._id) { - const department = departmentId - ? await LivechatDepartment.findOneById>(departmentId, { - projection: { fallbackForwardDepartment: 1, name: 1 }, - }) - : null; if (!department?.fallbackForwardDepartment?.length) { logger.debug(`Cannot forward room ${room._id}. Chat assigned to agent ${servedBy._id} (Previous was ${oldServedBy._id})`); throw new Error('error-no-agents-online-in-department'); diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 051053f761b1..5f0a458dc314 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -46,7 +46,7 @@ type Routing = { options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId?: string; transferData?: any } }, ): Promise<(IOmnichannelRoom & { chatQueued?: boolean }) | null | void>; assignAgent(inquiry: InquiryWithAgentInfo, agent: SelectedAgent): Promise; - unassignAgent(inquiry: ILivechatInquiryRecord, departmentId?: string): Promise; + unassignAgent(inquiry: ILivechatInquiryRecord, departmentId?: string, shouldQueue?: boolean): Promise; takeInquiry( inquiry: Omit< ILivechatInquiryRecord, @@ -158,7 +158,7 @@ export const RoutingManager: Routing = { return inquiry; }, - async unassignAgent(inquiry, departmentId) { + async unassignAgent(inquiry, departmentId, shouldQueue = false) { const { rid, department } = inquiry; const room = await LivechatRooms.findOneById(rid); @@ -181,6 +181,10 @@ export const RoutingManager: Routing = { const { servedBy } = room; + if (shouldQueue) { + await LivechatInquiry.queueInquiry(inquiry._id); + } + if (servedBy) { await LivechatRooms.removeAgentByRoomId(rid); await this.removeAllRoomSubscriptions(room); diff --git a/apps/meteor/app/livechat/server/methods/saveDepartment.ts b/apps/meteor/app/livechat/server/methods/saveDepartment.ts index dd83a294cb0e..45b3b2ec2168 100644 --- a/apps/meteor/app/livechat/server/methods/saveDepartment.ts +++ b/apps/meteor/app/livechat/server/methods/saveDepartment.ts @@ -21,6 +21,7 @@ declare module '@rocket.chat/ui-contexts' { chatClosingTags?: string[]; fallbackForwardDepartment?: string; departmentsAllowedToForward?: string[]; + allowReceiveForwardOffline?: boolean; }, departmentAgents?: | { diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx b/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx index 0f64e41d242f..30cad4142ff1 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx @@ -73,6 +73,7 @@ export type FormValues = { fallbackForwardDepartment: string; agentList: IDepartmentAgent[]; chatClosingTags: string[]; + allowReceiveForwardOffline: boolean; }; function withDefault(key: T | undefined | null, defaultValue: T) { @@ -96,6 +97,7 @@ const getInitialValues = ({ department, agents, allowedToForwardData }: InitialV fallbackForwardDepartment: withDefault(department?.fallbackForwardDepartment, ''), chatClosingTags: department?.chatClosingTags ?? [], agentList: agents || [], + allowReceiveForwardOffline: withDefault(department?.allowReceiveForwardOffline, false), }); function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmentProps) { @@ -151,6 +153,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen waitingQueueMessage, departmentsAllowedToForward, fallbackForwardDepartment, + allowReceiveForwardOffline, } = data; const payload = { @@ -169,6 +172,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen waitingQueueMessage, departmentsAllowedToForward: departmentsAllowedToForward?.map((dep) => dep.value), fallbackForwardDepartment, + allowReceiveForwardOffline, }; try { @@ -214,6 +218,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen const fallbackForwardDepartmentField = useUniqueId(); const requestTagBeforeClosingChatField = useUniqueId(); const chatClosingTagsField = useUniqueId(); + const allowReceiveForwardOffline = useUniqueId(); return ( @@ -424,6 +429,15 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen + + + {t('Accept_receive_inquiry_no_online_agents')} + + + + {t('Accept_receive_inquiry_no_online_agents_Hint')} + + {requestTagBeforeClosingChat && ( diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts index 6d0408dffc91..0e6a51cd0ff6 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts @@ -232,6 +232,7 @@ export const LivechatEnterprise = { chatClosingTags: Match.Optional([String]), fallbackForwardDepartment: Match.Optional(String), departmentsAllowedToForward: Match.Optional([String]), + allowReceiveForwardOffline: Match.Optional(Boolean), }; // The Livechat Form department support addition/custom fields, so those fields need to be added before validating diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts index 3d18f9c394b9..ba0df137b567 100644 --- a/apps/meteor/tests/data/livechat/department.ts +++ b/apps/meteor/tests/data/livechat/department.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings'; import { api, credentials, methodCall, request } from '../api-data'; import { IUserCredentialsHeader } from '../user'; -import { createAnOnlineAgent } from './users'; +import { createAnOnlineAgent, createAnOfflineAgent } from './users'; import { WithRequiredProperty } from './utils'; export const NewDepartmentData = ((): Partial => ({ @@ -29,7 +29,9 @@ export const updateDepartment = async (departmentId: string, departmentData: Par return response.body.department; }; -export const createDepartmentWithMethod = (initialAgents: { agentId: string, username: string }[] = []) => +export const createDepartmentWithMethod = ( + initialAgents: { agentId: string, username: string }[] = [], + allowReceiveForwardOffline = false) => new Promise((resolve, reject) => { request .post(methodCall('livechat:saveDepartment')) @@ -37,14 +39,19 @@ new Promise((resolve, reject) => { .send({ message: JSON.stringify({ method: 'livechat:saveDepartment', - params: ['', { - enabled: true, - email: faker.internet.email(), - showOnRegistration: true, - showOnOfflineForm: true, - name: `new department ${Date.now()}`, - description: 'created from api', - }, initialAgents], + params: [ + '', + { + enabled: true, + email: faker.internet.email(), + showOnRegistration: true, + showOnOfflineForm: true, + name: `new department ${Date.now()}`, + description: 'created from api', + allowReceiveForwardOffline, + }, + initialAgents, + ], id: 'id', msg: 'method', }), @@ -102,6 +109,31 @@ export const addOrRemoveAgentFromDepartment = async (departmentId: string, agent throw new Error('Failed to add or remove agent from department. Status code: ' + response.status + '\n' + response.body); } } +export const createDepartmentWithAnOfflineAgent = async ({ + allowReceiveForwardOffline = false, +}: { + allowReceiveForwardOffline: boolean; +}): Promise<{ + department: ILivechatDepartment; + agent: { + credentials: IUserCredentialsHeader; + user: WithRequiredProperty; + }; +}> => { + const { user, credentials } = await createAnOfflineAgent(); + + const department = (await createDepartmentWithMethod(undefined, allowReceiveForwardOffline)) as ILivechatDepartment; + + await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true); + + return { + department, + agent: { + credentials, + user, + }, + }; +}; export const archiveDepartment = async (departmentId: string): Promise => { await request.post(api(`livechat/department/${ departmentId }/archive`)).set(credentials).expect(200); diff --git a/apps/meteor/tests/data/livechat/users.ts b/apps/meteor/tests/data/livechat/users.ts index 3a21cbee923c..161c20749b6c 100644 --- a/apps/meteor/tests/data/livechat/users.ts +++ b/apps/meteor/tests/data/livechat/users.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import type { ILivechatAgent, IUser } from "@rocket.chat/core-typings"; import { IUserCredentialsHeader, password } from "../user"; import { createUser, login } from "../users.helper"; -import { createAgent, makeAgentAvailable } from "./rooms"; +import { createAgent, makeAgentAvailable, makeAgentUnavailable } from "./rooms"; import { api, credentials, request } from "../api-data"; export const createBotAgent = async (): Promise<{ @@ -57,3 +57,21 @@ export const createAnOnlineAgent = async (): Promise<{ user: agent, }; } + +export const createAnOfflineAgent = async (): Promise<{ + credentials: IUserCredentialsHeader; + user: IUser & { username: string }; +}> => { + const username = `user.test.${Date.now()}.offline`; + const email = `${username}.offline@rocket.chat`; + const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password }); + const agent = body.user; + const createdUserCredentials = await login(agent.username, password); + await createAgent(agent.username); + await makeAgentUnavailable(createdUserCredentials); + + return { + credentials: createdUserCredentials, + user: agent, + }; +}; \ No newline at end of file diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 0d9e5fff0a65..e99c893abf9a 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -18,7 +18,7 @@ import type { Response } from 'supertest'; import type { SuccessResult } from '../../../../app/api/server/definition'; import { getCredentials, api, request, credentials, methodCall } from '../../../data/api-data'; import { createCustomField } from '../../../data/livechat/custom-fields'; -import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; +import { createDepartmentWithAnOfflineAgent, createDepartmentWithAnOnlineAgent, deleteDepartment } from '../../../data/livechat/department'; import { createSLA, getRandomPriority } from '../../../data/livechat/priorities'; import { createVisitor, @@ -32,6 +32,7 @@ import { closeOmnichannelRoom, createDepartment, fetchMessages, + makeAgentUnavailable, } from '../../../data/livechat/rooms'; import { saveTags } from '../../../data/livechat/tags'; import type { DummyResponse } from '../../../data/livechat/utils'; @@ -700,6 +701,35 @@ describe('LIVECHAT - rooms', function () { await deleteUser(initialAgentAssignedToChat); await deleteUser(forwardChatToUser); }); + + (IS_EE ? it : it.skip)('should return error message when transferred to a offline agent', async () => { + await updateSetting('Livechat_Routing_Method', 'Auto_Selection'); + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: false }); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + await request + .post(api('livechat/room.forward')) + .set(credentials) + .send({ + roomId: newRoom._id, + departmentId: forwardToOfflineDepartment._id, + clientAction: true, + comment: 'test comment', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'error-no-agents-online-in-department'); + }); + + await deleteDepartment(initialDepartment._id); + await deleteDepartment(forwardToOfflineDepartment._id); + }); + (IS_EE ? it : it.skip)('should return a success message when transferred successfully to a department', async () => { const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); const { department: forwardToDepartment } = await createDepartmentWithAnOnlineAgent(); @@ -734,6 +764,112 @@ describe('LIVECHAT - rooms', function () { expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department'); expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToDepartment._id); }); + (IS_EE ? it : it.skip)( + 'should return a success message when transferred successfully to an offline department when the department accepts it', + async () => { + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: true }); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + await request + .post(api('livechat/room.forward')) + .set(credentials) + .send({ + roomId: newRoom._id, + departmentId: forwardToOfflineDepartment._id, + clientAction: true, + comment: 'test comment', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + await deleteDepartment(initialDepartment._id); + await deleteDepartment(forwardToOfflineDepartment._id); + }, + ); + (IS_EE ? it : it.skip)('inquiry should be taken automatically when agent on department is online again', async () => { + await updateSetting('Livechat_Routing_Method', 'Auto_Selection'); + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: true }); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + await request.post(api('livechat/room.forward')).set(credentials).send({ + roomId: newRoom._id, + departmentId: forwardToOfflineDepartment._id, + clientAction: true, + comment: 'test comment', + }); + + await makeAgentAvailable(); + + const latestRoom = await getLivechatRoomInfo(newRoom._id); + + expect(latestRoom).to.have.property('departmentId'); + expect(latestRoom.departmentId).to.be.equal(forwardToOfflineDepartment._id); + + expect(latestRoom).to.have.property('lastMessage'); + expect(latestRoom.lastMessage?.t).to.be.equal('livechat_transfer_history'); + expect(latestRoom.lastMessage?.u?.username).to.be.equal(adminUsername); + expect((latestRoom.lastMessage as any)?.transferData?.comment).to.be.equal('test comment'); + expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department'); + expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToOfflineDepartment._id); + + await updateSetting('Livechat_Routing_Method', 'Manual_Selection'); + await deleteDepartment(initialDepartment._id); + await deleteDepartment(forwardToOfflineDepartment._id); + }); + + (IS_EE ? it : it.skip)('when manager forward to offline department the inquiry should be set to the queue', async () => { + await updateSetting('Livechat_Routing_Method', 'Manual_Selection'); + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToOfflineDepartment, agent: offlineAgent } = await createDepartmentWithAnOfflineAgent({ + allowReceiveForwardOffline: true, + }); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + await makeAgentUnavailable(offlineAgent.credentials); + + const manager: IUser = await createUser(); + const managerCredentials = await login(manager.username, password); + await createManager(manager.username); + + await request.post(api('livechat/room.forward')).set(managerCredentials).send({ + roomId: newRoom._id, + departmentId: forwardToOfflineDepartment._id, + clientAction: true, + comment: 'test comment', + }); + + await request + .get(api(`livechat/queue`)) + .set(credentials) + .query({ + count: 1, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body.queue).to.be.an('array'); + expect(res.body.queue[0].chats).not.to.undefined; + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }); + + await deleteDepartment(initialDepartment._id); + await deleteDepartment(forwardToOfflineDepartment._id); + }); + let roomId: string; let visitorToken: string; (IS_EE ? it : it.skip)('should return a success message when transferring to a fallback department', async () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index 54f8739efee3..fc9af8d4580e 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -49,7 +49,16 @@ import { IS_EE } from '../../../e2e/config/constants'; const { body } = await request .post(api('livechat/department')) .set(credentials) - .send({ department: { name: 'Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' } }) + .send({ + department: { + name: 'Test', + enabled: true, + showOnOfflineForm: true, + showOnRegistration: true, + email: 'bla@bla', + allowReceiveForwardOffline: true, + }, + }) .expect('Content-Type', 'application/json') .expect(200); expect(body).to.have.property('success', true); @@ -59,6 +68,8 @@ import { IS_EE } from '../../../e2e/config/constants'; expect(body.department).to.have.property('enabled', true); expect(body.department).to.have.property('showOnOfflineForm', true); expect(body.department).to.have.property('showOnRegistration', true); + expect(body.department).to.have.property('allowReceiveForwardOffline', true); + departmentId = body.department._id; }); diff --git a/packages/core-typings/src/ILivechatDepartment.ts b/packages/core-typings/src/ILivechatDepartment.ts index 8c75913dc729..a73cf55cb235 100644 --- a/packages/core-typings/src/ILivechatDepartment.ts +++ b/packages/core-typings/src/ILivechatDepartment.ts @@ -17,6 +17,7 @@ export interface ILivechatDepartment { departmentsAllowedToForward?: string[]; maxNumberSimultaneousChat?: number; ancestors?: string[]; + allowReceiveForwardOffline?: boolean; // extra optional fields [k: string]: any; } @@ -32,4 +33,5 @@ export type LivechatDepartmentDTO = { chatClosingTags?: string[] | undefined; fallbackForwardDepartment?: string | undefined; departmentsAllowedToForward?: string[] | undefined; + allowReceiveForwardOffline?: boolean; }; diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 84cc8a1965f2..6a13985f8a27 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -4841,6 +4841,8 @@ "Show_more": "Show more", "Show_name_field": "Show name field", "show_offline_users": "show offline users", + "Accept_receive_inquiry_no_online_agents": "Allow department to receive forwarded inquiries even when there's no available agents", + "Accept_receive_inquiry_no_online_agents_Hint": "This method is effective only with automatic assignment routing methods, and does not apply to Manual Selection.", "Show_on_offline_page": "Show on offline page", "Show_on_registration_page": "Show on registration page", "Show_only_online": "Show Online Only", From a7823c1b0901c510af5bfa994e7b3f96ee10dd91 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 5 Apr 2024 16:13:16 -0300 Subject: [PATCH 13/16] chore: move imported `Apps` to proxy (#32142) --- .../authentication/server/startup/index.js | 6 +- .../app/file-upload/server/lib/FileUpload.ts | 12 +--- .../app/lib/server/functions/addUserToRoom.ts | 4 +- .../lib/server/functions/createDirectRoom.ts | 8 +-- .../app/lib/server/functions/createRoom.ts | 6 +- .../app/lib/server/functions/deleteMessage.ts | 2 +- .../server/functions/removeUserFromRoom.ts | 4 +- .../app/lib/server/functions/saveUser.js | 2 +- .../app/lib/server/functions/sendMessage.ts | 4 +- .../app/lib/server/functions/updateMessage.ts | 4 +- .../server/methods/deleteUserOwnAccount.ts | 2 +- apps/meteor/app/livechat/server/lib/Helper.ts | 6 +- .../app/livechat/server/lib/LivechatTyped.ts | 8 +-- .../app/livechat/server/lib/QueueManager.ts | 2 +- .../app/livechat/server/lib/RoutingManager.ts | 2 +- apps/meteor/app/mailer/server/api.ts | 2 +- .../app/message-pin/server/pinMessage.ts | 4 +- .../app/message-star/server/starMessage.ts | 2 +- .../app/reactions/server/setReaction.ts | 2 +- .../server/lib/getAppsStatistics.js | 6 +- .../threads/server/methods/followMessage.ts | 2 +- .../threads/server/methods/unfollowMessage.ts | 2 +- .../ee/server/apps/communication/rest.ts | 5 +- apps/meteor/ee/server/index.ts | 1 + apps/meteor/ee/server/startup/index.ts | 1 - .../server/lib/moderation/reportMessage.ts | 2 +- apps/meteor/server/methods/deleteUser.ts | 2 +- apps/meteor/server/methods/eraseRoom.ts | 4 +- apps/meteor/server/methods/logoutCleanUp.ts | 2 +- apps/meteor/server/methods/reportMessage.ts | 2 +- apps/meteor/server/methods/saveUserProfile.ts | 2 +- .../server/services/apps-engine/service.ts | 66 ++++++++++--------- .../services/video-conference/service.ts | 4 +- apps/meteor/server/startup/migrations/v291.ts | 2 +- apps/meteor/server/startup/migrations/v292.ts | 2 +- apps/meteor/server/startup/migrations/v294.ts | 2 +- packages/apps/src/orchestrator.ts | 26 +++++++- 37 files changed, 115 insertions(+), 100 deletions(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index ab622be95d53..c4f568f3c4f8 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -350,8 +350,8 @@ const insertUserDocAsync = async function (options, user) { if (!options.skipAppsEngineEvent) { // `post` triggered events don't need to wait for the promise to resolve - Apps?.triggerEvent(AppEvents.IPostUserCreated, { user, performedBy: await safeGetMeteorUser() }).catch((e) => { - Apps?.getRocketChatLogger().error('Error while executing post user created event:', e); + Apps.self?.triggerEvent(AppEvents.IPostUserCreated, { user, performedBy: await safeGetMeteorUser() }).catch((e) => { + Apps.self?.getRocketChatLogger().error('Error while executing post user created event:', e); }); } @@ -424,7 +424,7 @@ const validateLoginAttemptAsync = async function (login) { */ if (login.type !== 'resume') { // App IPostUserLoggedIn event hook - await Apps?.triggerEvent(AppEvents.IPostUserLoggedIn, login.user); + await Apps.self?.triggerEvent(AppEvents.IPostUserLoggedIn, login.user); } return true; diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index 3fd00f5e3e2a..4458f9d61881 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -177,7 +177,7 @@ export const FileUpload = { // App IPreFileUpload event hook try { - await Apps?.triggerEvent(AppEvents.IPreFileUpload, { file, content: content || Buffer.from([]) }); + await Apps.self?.triggerEvent(AppEvents.IPreFileUpload, { file, content: content || Buffer.from([]) }); } catch (error: any) { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); @@ -587,15 +587,7 @@ export const FileUpload = { } // eslint-disable-next-line prettier/prettier - const headersToProxy = [ - 'age', - 'cache-control', - 'content-length', - 'content-type', - 'date', - 'expired', - 'last-modified', - ]; + const headersToProxy = ['age', 'cache-control', 'content-length', 'content-type', 'date', 'expired', 'last-modified']; headersToProxy.forEach((header) => { fileRes.headers[header] && res.setHeader(header, String(fileRes.headers[header])); diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts index 4a70943d28e2..4506b659bf4d 100644 --- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts +++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts @@ -54,7 +54,7 @@ export const addUserToRoom = async function ( } try { - await Apps?.triggerEvent(AppEvents.IPreRoomUserJoined, room, userToBeAdded, inviter); + await Apps.self?.triggerEvent(AppEvents.IPreRoomUserJoined, room, userToBeAdded, inviter); } catch (error: any) { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); @@ -118,7 +118,7 @@ export const addUserToRoom = async function ( // Keep the current event await callbacks.run('afterJoinRoom', userToBeAdded, room); - void Apps?.triggerEvent(AppEvents.IPostRoomUserJoined, room, userToBeAdded, inviter); + void Apps.self?.triggerEvent(AppEvents.IPostRoomUserJoined, room, userToBeAdded, inviter); }); } diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index dea6004eb4e5..c1de81332543 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -103,7 +103,7 @@ export async function createDirectRoom( _USERNAMES: usernames, }; - const prevent = await Apps?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmpRoom).catch((error) => { + const prevent = await Apps.self?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmpRoom).catch((error) => { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); } @@ -115,9 +115,9 @@ export async function createDirectRoom( throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); } - const result = await Apps?.triggerEvent( + const result = await Apps.self?.triggerEvent( AppEvents.IPreRoomCreateModify, - await Apps?.triggerEvent(AppEvents.IPreRoomCreateExtend, tmpRoom), + await Apps.self?.triggerEvent(AppEvents.IPreRoomCreateExtend, tmpRoom), ); if (typeof result === 'object') { @@ -172,7 +172,7 @@ export async function createDirectRoom( await callbacks.run('afterCreateDirectRoom', insertedRoom, { members: roomMembers, creatorId: options?.creator }); - void Apps?.triggerEvent(AppEvents.IPostRoomCreate, insertedRoom); + void Apps.self?.triggerEvent(AppEvents.IPostRoomCreate, insertedRoom); } return { diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 11577a76c4fb..615a0faf8bb3 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -198,7 +198,7 @@ export const createRoom = async ( _USERNAMES: members, }; - const prevent = await Apps?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmp).catch((error) => { + const prevent = await Apps.self?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmp).catch((error) => { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); } @@ -210,7 +210,7 @@ export const createRoom = async ( throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); } - const eventResult = await Apps?.triggerEvent( + const eventResult = await Apps.self?.triggerEvent( AppEvents.IPreRoomCreateModify, await Apps.triggerEvent(AppEvents.IPreRoomCreateExtend, tmp), ); @@ -245,7 +245,7 @@ export const createRoom = async ( callbacks.runAsync('federation.afterCreateFederatedRoom', room, { owner, originalMemberList: members }); } - void Apps?.triggerEvent(AppEvents.IPostRoomCreate, room); + void Apps.self?.triggerEvent(AppEvents.IPostRoomCreate, room); return { rid: room._id, // backwards compatible inserted: true, diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index 26677bf37fff..9368787bf7ea 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -33,7 +33,7 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise 0; const keepHistory = settings.get('Message_KeepHistory') || isThread; const showDeletedStatus = settings.get('Message_ShowDeletedStatus') || isThread; - const bridges = Apps?.isLoaded() && Apps.getBridges(); + const bridges = Apps.self?.isLoaded() && Apps.getBridges(); if (deletedMsg && bridges) { const prevent = await bridges.getListenerBridge().messageEvent(AppEvents.IPreMessageDeletePrevent, deletedMsg); diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts index aaff8257f987..1cc8c4ad5432 100644 --- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts +++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts @@ -20,7 +20,7 @@ export const removeUserFromRoom = async function ( } try { - await Apps?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user); + await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user); } catch (error: any) { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); @@ -67,5 +67,5 @@ export const removeUserFromRoom = async function ( // TODO: CACHE: maybe a queue? await afterLeaveRoomCallback.run(user, room); - await Apps?.triggerEvent(AppEvents.IPostRoomUserLeave, room, user); + await Apps.self?.triggerEvent(AppEvents.IPostRoomUserLeave, room, user); }; diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index 667b5179ceea..f34a54432c4f 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -434,7 +434,7 @@ export const saveUser = async function (userId, userData) { oldUser: oldUserData, }); - await Apps?.triggerEvent(AppEvents.IPostUserUpdated, { + await Apps.self?.triggerEvent(AppEvents.IPostUserUpdated, { user: userUpdated, previousUser: oldUserData, performedBy: await safeGetMeteorUser(), diff --git a/apps/meteor/app/lib/server/functions/sendMessage.ts b/apps/meteor/app/lib/server/functions/sendMessage.ts index 14da152c87f9..089d33f98034 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.ts +++ b/apps/meteor/app/lib/server/functions/sendMessage.ts @@ -225,7 +225,7 @@ export const sendMessage = async function (user: any, message: any, room: any, u } // For the Rocket.Chat Apps :) - if (Apps?.isLoaded()) { + if (Apps.self?.isLoaded()) { const listenerBridge = Apps.getBridges()?.getListenerBridge(); const prevent = await listenerBridge?.messageEvent('IPreMessageSentPrevent', message); @@ -275,7 +275,7 @@ export const sendMessage = async function (user: any, message: any, room: any, u message._id = insertedId; } - if (Apps?.isLoaded()) { + if (Apps.self?.isLoaded()) { // This returns a promise, but it won't mutate anything about the message // so, we don't really care if it is successful or fails void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageSent', message); diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index 14d93d2874e9..6b3cb9ef64f5 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -23,7 +23,7 @@ export const updateMessage = async function ( let messageData: IMessage = Object.assign({}, originalMessage, message); // For the Rocket.Chat Apps :) - if (message && Apps && Apps.isLoaded()) { + if (message && Apps.self && Apps.isLoaded()) { const prevent = await Apps.getBridges().getListenerBridge().messageEvent(AppEvents.IPreMessageUpdatedPrevent, messageData); if (prevent) { throw new Meteor.Error('error-app-prevented-updating', 'A Rocket.Chat App prevented the message updating.'); @@ -76,7 +76,7 @@ export const updateMessage = async function ( }, ); - if (Apps?.isLoaded()) { + if (Apps.self?.isLoaded()) { // This returns a promise, but it won't mutate anything about the message // so, we don't really care if it is successful or fails void Apps.getBridges()?.getListenerBridge().messageEvent(AppEvents.IPostMessageUpdated, messageData); diff --git a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts index f30182def68e..2d651950da19 100644 --- a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts +++ b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts @@ -66,7 +66,7 @@ Meteor.methods({ await deleteUser(uid, confirmRelinquish); // App IPostUserDeleted event hook - await Apps?.triggerEvent(AppEvents.IPostUserDeleted, { user }); + await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user }); return true; }, diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 3c1d601250c1..453869d4425a 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -273,7 +273,7 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); setImmediate(() => { - void Apps?.triggerEvent(AppEvents.IPostLivechatAgentUnassigned, { room, user }); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatAgentUnassigned, { room, user }); }); }; @@ -452,7 +452,7 @@ export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: T } setImmediate(() => { - void Apps?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { + void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { type: LivechatTransferEventType.AGENT, room: rid, from: oldServedBy?._id, @@ -482,7 +482,7 @@ export const updateChatDepartment = async ({ ]); setImmediate(() => { - void Apps?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { + void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { type: LivechatTransferEventType.DEPARTMENT, room: rid, from: oldDepartmentId, diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 39d3467fbaf2..dc5aa506f405 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -329,8 +329,8 @@ class LivechatClass { * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed * in the next major version of the Apps-Engine */ - void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); - void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); + void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); + void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); }); if (process.env.TEST_MODE) { await callbacks.run('livechat.closeRoom', { @@ -1426,7 +1426,7 @@ class LivechatClass { const ret = await LivechatVisitors.saveGuestById(_id, updateData); setImmediate(() => { - void Apps?.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id); }); return ret; @@ -1792,7 +1792,7 @@ class LivechatClass { await LivechatRooms.saveRoomById(roomData); setImmediate(() => { - void Apps?.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); }); if (guestData?.name?.trim().length) { diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 8a11a36238fa..8be71aa4c991 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -105,7 +105,7 @@ export const QueueManager: queueManager = { throw new Error('inquiry-not-found'); } - void Apps?.triggerEvent(AppEvents.IPostLivechatRoomStarted, room); + void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomStarted, room); await LivechatRooms.updateRoomCount(); await queueInquiry(inquiry, agent); diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 5f0a458dc314..19437d800ee2 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -154,7 +154,7 @@ export const RoutingManager: Routing = { await dispatchAgentDelegated(rid, agent.agentId); logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`); - void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user }); + void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user }); return inquiry; }, diff --git a/apps/meteor/app/mailer/server/api.ts b/apps/meteor/app/mailer/server/api.ts index cc2caae74ba6..e562fc8e7b39 100644 --- a/apps/meteor/app/mailer/server/api.ts +++ b/apps/meteor/app/mailer/server/api.ts @@ -170,7 +170,7 @@ export const sendNoWrap = async ({ const email = { to, from, replyTo, subject, html, text, headers }; - const eventResult = await Apps?.triggerEvent(AppEvents.IPreEmailSent, { email }); + const eventResult = await Apps.self?.triggerEvent(AppEvents.IPreEmailSent, { email }); setImmediate(() => Email.sendAsync(eventResult || email).catch((e) => console.error(e))); }; diff --git a/apps/meteor/app/message-pin/server/pinMessage.ts b/apps/meteor/app/message-pin/server/pinMessage.ts index 4887e3603122..dc17a75a0192 100644 --- a/apps/meteor/app/message-pin/server/pinMessage.ts +++ b/apps/meteor/app/message-pin/server/pinMessage.ts @@ -129,7 +129,7 @@ Meteor.methods({ } // App IPostMessagePinned event hook - await Apps?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); + await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); const msgId = await Message.saveSystemMessage('message_pinned', originalMessage.rid, '', me, { attachments: [ @@ -216,7 +216,7 @@ Meteor.methods({ } // App IPostMessagePinned event hook - await Apps?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); + await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned); if (settings.get('Message_Read_Receipt_Store_Users')) { diff --git a/apps/meteor/app/message-star/server/starMessage.ts b/apps/meteor/app/message-star/server/starMessage.ts index 9f8ba75c4536..7ac8fd619d31 100644 --- a/apps/meteor/app/message-star/server/starMessage.ts +++ b/apps/meteor/app/message-star/server/starMessage.ts @@ -57,7 +57,7 @@ Meteor.methods({ await Rooms.updateLastMessageStar(room._id, uid, message.starred); } - await Apps?.triggerEvent(AppEvents.IPostMessageStarred, message, await Meteor.userAsync(), message.starred); + await Apps.self?.triggerEvent(AppEvents.IPostMessageStarred, message, await Meteor.userAsync(), message.starred); await Messages.updateUserStarById(message._id, uid, message.starred); diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index ed2271a5d4d0..36eaab695512 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -106,7 +106,7 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction isReacted = true; } - await Apps?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); + await Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); void broadcastMessageFromData({ id: message._id, diff --git a/apps/meteor/app/statistics/server/lib/getAppsStatistics.js b/apps/meteor/app/statistics/server/lib/getAppsStatistics.js index 652686e6715c..1d84bead3e85 100644 --- a/apps/meteor/app/statistics/server/lib/getAppsStatistics.js +++ b/apps/meteor/app/statistics/server/lib/getAppsStatistics.js @@ -6,10 +6,10 @@ import { Info } from '../../../utils/rocketchat.info'; export function getAppsStatistics() { return { engineVersion: Info.marketplaceApiVersion, - totalInstalled: (Apps?.isInitialized() && Apps.getManager().get().length) ?? 0, - totalActive: (Apps?.isInitialized() && Apps.getManager().get({ enabled: true }).length) ?? 0, + totalInstalled: (Apps.self?.isInitialized() && Apps.getManager().get().length) ?? 0, + totalActive: (Apps.self?.isInitialized() && Apps.getManager().get({ enabled: true }).length) ?? 0, totalFailed: - (Apps?.isInitialized() && + (Apps.self?.isInitialized() && Apps.getManager() .get({ disabled: true }) .filter(({ app: { status } }) => status !== AppStatus.MANUALLY_DISABLED).length) ?? diff --git a/apps/meteor/app/threads/server/methods/followMessage.ts b/apps/meteor/app/threads/server/methods/followMessage.ts index f6bae69b1aaa..05650d0ad2ef 100644 --- a/apps/meteor/app/threads/server/methods/followMessage.ts +++ b/apps/meteor/app/threads/server/methods/followMessage.ts @@ -44,7 +44,7 @@ Meteor.methods({ const followResult = await follow({ tmid: message.tmid || message._id, uid }); const isFollowed = true; - await Apps?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); + await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); return followResult; }, diff --git a/apps/meteor/app/threads/server/methods/unfollowMessage.ts b/apps/meteor/app/threads/server/methods/unfollowMessage.ts index b50c26508ebc..afc9206b038f 100644 --- a/apps/meteor/app/threads/server/methods/unfollowMessage.ts +++ b/apps/meteor/app/threads/server/methods/unfollowMessage.ts @@ -44,7 +44,7 @@ Meteor.methods({ const unfollowResult = await unfollow({ rid: message.rid, tmid: message.tmid || message._id, uid }); const isFollowed = false; - await Apps?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); + await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); return unfollowResult; }, diff --git a/apps/meteor/ee/server/apps/communication/rest.ts b/apps/meteor/ee/server/apps/communication/rest.ts index ea259fef5f0c..df30cccc8e73 100644 --- a/apps/meteor/ee/server/apps/communication/rest.ts +++ b/apps/meteor/ee/server/apps/communication/rest.ts @@ -534,10 +534,7 @@ export class AppsRestApi { try { const { event, externalComponent } = this.bodyParams; - const result = (Apps?.getBridges()?.getListenerBridge() as Record).externalComponentEvent( - event, - externalComponent, - ); + const result = (Apps.getBridges()?.getListenerBridge() as Record).externalComponentEvent(event, externalComponent); return API.v1.success({ result }); } catch (e: any) { diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index f00caa896e43..0a9ad57cb00f 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -12,5 +12,6 @@ import './requestSeatsRoute'; import './configuration/index'; import './local-services/ldap/service'; import './methods/getReadReceipts'; +import './apps/startup'; export { registerEEBroker } from './startup'; diff --git a/apps/meteor/ee/server/startup/index.ts b/apps/meteor/ee/server/startup/index.ts index ade83ea57227..a8091f0e9a37 100644 --- a/apps/meteor/ee/server/startup/index.ts +++ b/apps/meteor/ee/server/startup/index.ts @@ -1,4 +1,3 @@ -import '../apps/startup'; import '../../app/authorization/server'; import './apps'; import './audit'; diff --git a/apps/meteor/server/lib/moderation/reportMessage.ts b/apps/meteor/server/lib/moderation/reportMessage.ts index 710ea6e1b685..a546896b8332 100644 --- a/apps/meteor/server/lib/moderation/reportMessage.ts +++ b/apps/meteor/server/lib/moderation/reportMessage.ts @@ -49,7 +49,7 @@ export const reportMessage = async (messageId: IMessage['_id'], description: str await ModerationReports.createWithMessageDescriptionAndUserId(message, description, roomInfo, reportedBy); - await Apps?.triggerEvent(AppEvents.IPostMessageReported, message, user, description); + await Apps.self?.triggerEvent(AppEvents.IPostMessageReported, message, user, description); return true; }; diff --git a/apps/meteor/server/methods/deleteUser.ts b/apps/meteor/server/methods/deleteUser.ts index 8762cfab2437..e8b1f6eeed65 100644 --- a/apps/meteor/server/methods/deleteUser.ts +++ b/apps/meteor/server/methods/deleteUser.ts @@ -52,7 +52,7 @@ Meteor.methods({ await deleteUser(userId, confirmRelinquish, uid); // App IPostUserDeleted event hook - await Apps?.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Meteor.userAsync() }); + await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Meteor.userAsync() }); return true; }, diff --git a/apps/meteor/server/methods/eraseRoom.ts b/apps/meteor/server/methods/eraseRoom.ts index 687b9ad66992..b9b4833ad67f 100644 --- a/apps/meteor/server/methods/eraseRoom.ts +++ b/apps/meteor/server/methods/eraseRoom.ts @@ -35,7 +35,7 @@ export async function eraseRoom(rid: string, uid: string): Promise { }); } - if (Apps?.isLoaded()) { + if (Apps.self?.isLoaded()) { const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPreRoomDeletePrevent, room); if (prevent) { throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the room erasing.'); @@ -53,7 +53,7 @@ export async function eraseRoom(rid: string, uid: string): Promise { } } - if (Apps?.isLoaded()) { + if (Apps.self?.isLoaded()) { void Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPostRoomDeleted, room); } } diff --git a/apps/meteor/server/methods/logoutCleanUp.ts b/apps/meteor/server/methods/logoutCleanUp.ts index 502cad3c5fbf..359933faccbd 100644 --- a/apps/meteor/server/methods/logoutCleanUp.ts +++ b/apps/meteor/server/methods/logoutCleanUp.ts @@ -22,6 +22,6 @@ Meteor.methods({ }); // App IPostUserLogout event hook - await Apps?.triggerEvent(AppEvents.IPostUserLoggedOut, user); + await Apps.self?.triggerEvent(AppEvents.IPostUserLoggedOut, user); }, }); diff --git a/apps/meteor/server/methods/reportMessage.ts b/apps/meteor/server/methods/reportMessage.ts index 44087dad0424..05ac5aaf7e7b 100644 --- a/apps/meteor/server/methods/reportMessage.ts +++ b/apps/meteor/server/methods/reportMessage.ts @@ -77,7 +77,7 @@ Meteor.methods({ await ModerationReports.createWithMessageDescriptionAndUserId(message, description, roomInfo, reportedBy); - await Apps?.triggerEvent(AppEvents.IPostMessageReported, message, await Meteor.userAsync(), description); + await Apps.self?.triggerEvent(AppEvents.IPostMessageReported, message, await Meteor.userAsync(), description); return true; }, diff --git a/apps/meteor/server/methods/saveUserProfile.ts b/apps/meteor/server/methods/saveUserProfile.ts index 695742977ad3..c2ed41adaab9 100644 --- a/apps/meteor/server/methods/saveUserProfile.ts +++ b/apps/meteor/server/methods/saveUserProfile.ts @@ -156,7 +156,7 @@ async function saveUserProfile( // App IPostUserUpdated event hook const updatedUser = await Users.findOneById(this.userId); - await Apps?.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); + await Apps.self?.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); return true; } diff --git a/apps/meteor/server/services/apps-engine/service.ts b/apps/meteor/server/services/apps-engine/service.ts index 7e36a937e6a6..41a53cf5bbb6 100644 --- a/apps/meteor/server/services/apps-engine/service.ts +++ b/apps/meteor/server/services/apps-engine/service.ts @@ -16,7 +16,7 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi super(); this.onEvent('presence.status', async ({ user, previousStatus }): Promise => { - await Apps?.triggerEvent(AppEvents.IPostUserStatusChanged, { + await Apps.self?.triggerEvent(AppEvents.IPostUserStatusChanged, { user, currentStatus: user.status, previousStatus, @@ -24,70 +24,72 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi }); this.onEvent('apps.added', async (appId: string): Promise => { - Apps?.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`); + Apps.self?.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`); // if the app already exists in this instance, don't load it again - const app = Apps?.getManager()?.getOneById(appId); + const app = Apps.self?.getManager()?.getOneById(appId); if (app) { - Apps?.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`); + Apps.self?.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`); return; } - await Apps?.getManager()?.addLocal(appId); + await Apps.self?.getManager()?.addLocal(appId); }); this.onEvent('apps.removed', async (appId: string): Promise => { - Apps?.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`); - const app = Apps?.getManager()?.getOneById(appId); + Apps.self?.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`); + const app = Apps.self?.getManager()?.getOneById(appId); if (!app) { - Apps?.getRocketChatLogger().info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`); + Apps.self + ?.getRocketChatLogger() + .info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`); return; } - await Apps?.getManager()?.removeLocal(appId); + await Apps.self?.getManager()?.removeLocal(appId); }); this.onEvent('apps.updated', async (appId: string): Promise => { - Apps?.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`); - const storageItem = await Apps?.getStorage()?.retrieveOne(appId); + Apps.self?.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`); + const storageItem = await Apps.self?.getStorage()?.retrieveOne(appId); if (!storageItem) { - Apps?.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`); + Apps.self?.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`); return; } - const appPackage = await Apps?.getAppSourceStorage()?.fetch(storageItem); + const appPackage = await Apps.self?.getAppSourceStorage()?.fetch(storageItem); if (!appPackage) { return; } - await Apps?.getManager()?.updateLocal(storageItem, appPackage); + await Apps.self?.getManager()?.updateLocal(storageItem, appPackage); }); this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise => { - Apps?.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`); - const app = Apps?.getManager()?.getOneById(appId); + Apps.self?.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`); + const app = Apps.self?.getManager()?.getOneById(appId); if (!app) { - Apps?.getRocketChatLogger().info( - `"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`, - ); + Apps.self + ?.getRocketChatLogger() + .info(`"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`); return; } if (app.getStatus() === status) { - Apps?.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`); + Apps.self?.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`); return; } if (AppStatusUtils.isEnabled(status)) { - await Apps?.getManager()?.enable(appId).catch(SystemLogger.error); + await Apps.self?.getManager()?.enable(appId).catch(SystemLogger.error); } else if (AppStatusUtils.isDisabled(status)) { - await Apps?.getManager()?.disable(appId, status, true).catch(SystemLogger.error); + await Apps.self?.getManager()?.disable(appId, status, true).catch(SystemLogger.error); } }); this.onEvent('apps.settingUpdated', async (appId: string, setting): Promise => { - Apps?.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting }); - const app = Apps?.getManager()?.getOneById(appId); + Apps.self?.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting }); + const app = Apps.self?.getManager()?.getOneById(appId); const oldSetting = app?.getStorageItem().settings[setting.id].value; // avoid updating the setting if the value is the same, @@ -96,30 +98,32 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi // so we need to convert it to JSON stringified to compare it if (JSON.stringify(oldSetting) === JSON.stringify(setting.value)) { - Apps?.getRocketChatLogger().info( - `"apps.settingUpdated" event received for setting ${setting.id} of app "${appId}", but the setting value is the same`, - ); + Apps.self + ?.getRocketChatLogger() + .info(`"apps.settingUpdated" event received for setting ${setting.id} of app "${appId}", but the setting value is the same`); return; } - await Apps?.getManager() + await Apps.self + ?.getManager() ?.getSettingsManager() .updateAppSetting(appId, setting as any); }); } isInitialized(): boolean { - return Boolean(Apps?.isInitialized()); + return Boolean(Apps.self?.isInitialized()); } async getApps(query: IGetAppsFilter): Promise { - return Apps?.getManager() + return Apps.self + ?.getManager() ?.get(query) .map((app) => app.getApp().getInfo()); } async getAppStorageItemById(appId: string): Promise { - const app = Apps?.getManager()?.getOneById(appId); + const app = Apps.self?.getManager()?.getOneById(appId); if (!app) { return; diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts index 87fe279d0d94..7c7d5950cf5f 100644 --- a/apps/meteor/server/services/video-conference/service.ts +++ b/apps/meteor/server/services/video-conference/service.ts @@ -828,11 +828,11 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf } private async getProviderManager(): Promise { - if (!Apps?.isLoaded()) { + if (!Apps.self?.isLoaded()) { throw new Error('apps-engine-not-loaded'); } - const manager = Apps?.getManager()?.getVideoConfProviderManager(); + const manager = Apps.self?.getManager()?.getVideoConfProviderManager(); if (!manager) { throw new Error(availabilityErrors.NO_APP); } diff --git a/apps/meteor/server/startup/migrations/v291.ts b/apps/meteor/server/startup/migrations/v291.ts index f4fdbb743447..3f94c26dc6e0 100644 --- a/apps/meteor/server/startup/migrations/v291.ts +++ b/apps/meteor/server/startup/migrations/v291.ts @@ -12,7 +12,7 @@ addMigration({ await Settings.removeById('Apps_Framework_Development_Mode'); await Settings.removeById('Apps_Framework_enabled'); - if (!Apps) { + if (!Apps.self) { throw new Error('Apps Orchestrator not registered.'); } diff --git a/apps/meteor/server/startup/migrations/v292.ts b/apps/meteor/server/startup/migrations/v292.ts index beec6967a904..ac523f1b197e 100644 --- a/apps/meteor/server/startup/migrations/v292.ts +++ b/apps/meteor/server/startup/migrations/v292.ts @@ -9,7 +9,7 @@ addMigration({ version: 292, name: 'Add checksum signature to existing apps', async up() { - if (!Apps) { + if (!Apps.self) { throw new Error('Apps Orchestrator not registered.'); } diff --git a/apps/meteor/server/startup/migrations/v294.ts b/apps/meteor/server/startup/migrations/v294.ts index 832043740f89..8523db89e4b9 100644 --- a/apps/meteor/server/startup/migrations/v294.ts +++ b/apps/meteor/server/startup/migrations/v294.ts @@ -8,7 +8,7 @@ import { addMigration } from '../../lib/migrations'; addMigration({ version: 294, async up() { - if (!Apps) { + if (!Apps.self) { throw new Error('Apps Orchestrator not registered.'); } diff --git a/packages/apps/src/orchestrator.ts b/packages/apps/src/orchestrator.ts index 4e3a53d9d5f0..c0dee1609113 100644 --- a/packages/apps/src/orchestrator.ts +++ b/packages/apps/src/orchestrator.ts @@ -1,7 +1,29 @@ import type { IAppServerOrchestrator } from './IAppServerOrchestrator'; -export let Apps: IAppServerOrchestrator | undefined; +let app: IAppServerOrchestrator | undefined; + +type Orchestrator = { self: undefined } | (IAppServerOrchestrator & { self: IAppServerOrchestrator }); + +export const Apps = new Proxy({} as Orchestrator, { + get: (_target, nameProp: keyof IAppServerOrchestrator | 'self'): any => { + if (nameProp === 'self') { + return app; + } + + if (!app) { + throw new Error(`Orchestrator not found`); + } + + const prop = app[nameProp]; + + if (typeof prop === 'function') { + return prop.bind(app); + } + + return prop; + }, +}); export function registerOrchestrator(orch: IAppServerOrchestrator): void { - Apps = orch; + app = orch; } From 86bc4ca08cdd5aceac25bf2acba447a04400ec23 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 8 Apr 2024 10:16:10 -0300 Subject: [PATCH 14/16] regression(fuselage-ui-kit): Use default translation namespace for `-core` apps (#32105) --- packages/fuselage-ui-kit/jest.config.ts | 27 ++++ packages/fuselage-ui-kit/package.json | 8 +- .../src/hooks/useAppTranslation.spec.tsx | 139 ++++++++++++++++++ .../src/hooks/useAppTranslation.ts | 2 +- yarn.lock | 78 +++++++++- 5 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 packages/fuselage-ui-kit/jest.config.ts create mode 100644 packages/fuselage-ui-kit/src/hooks/useAppTranslation.spec.tsx diff --git a/packages/fuselage-ui-kit/jest.config.ts b/packages/fuselage-ui-kit/jest.config.ts new file mode 100644 index 000000000000..23a14f54fde9 --- /dev/null +++ b/packages/fuselage-ui-kit/jest.config.ts @@ -0,0 +1,27 @@ +export default { + preset: 'ts-jest', + errorOnDeprecated: true, + testEnvironment: 'jsdom', + modulePathIgnorePatterns: ['/dist/'], + transform: { + '^.+\\.(t|j)sx?$': [ + '@swc/jest', + { + sourceMaps: true, + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + decorators: false, + dynamicImport: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + }, +}; diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 216efd02ef54..927948921659 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -32,6 +32,7 @@ ".:build:clean": "rimraf dist", ".:build:esm": "tsc -p tsconfig-esm.json", ".:build:cjs": "tsc -p tsconfig-cjs.json", + "test": "jest", "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "typecheck": "tsc --noEmit", "docs": "cross-env NODE_ENV=production build-storybook -o ../../static/fuselage-ui-kit", @@ -81,6 +82,8 @@ "@storybook/source-loader": "~6.5.16", "@storybook/theming": "~6.5.16", "@tanstack/react-query": "^4.16.1", + "@testing-library/react": "^14.2.2", + "@testing-library/react-hooks": "^8.0.1", "@types/babel__core": "^7.20.3", "@types/babel__preset-env": "^7.9.4", "@types/react": "~17.0.69", @@ -88,14 +91,17 @@ "babel-loader": "~8.2.5", "cross-env": "^7.0.3", "eslint": "~8.45.0", + "i18next": "^23.10.1", + "jest": "^29.7.0", "normalize.css": "^8.0.1", "npm-run-all": "^4.1.5", "prettier": "~2.8.8", "react-docgen-typescript-plugin": "~1.0.5", "react-dom": "^17.0.2", - "react-i18next": "~13.2.2", + "react-i18next": "^14.1.0", "rimraf": "^3.0.2", "storybook-dark-mode": "~3.0.1", + "ts-jest": "^29.1.2", "tslib": "^2.5.3", "typescript": "~5.3.3" }, diff --git a/packages/fuselage-ui-kit/src/hooks/useAppTranslation.spec.tsx b/packages/fuselage-ui-kit/src/hooks/useAppTranslation.spec.tsx new file mode 100644 index 000000000000..fe84b1ffb771 --- /dev/null +++ b/packages/fuselage-ui-kit/src/hooks/useAppTranslation.spec.tsx @@ -0,0 +1,139 @@ +import { renderHook } from '@testing-library/react-hooks'; +import * as i18next from 'i18next'; +import type { FunctionComponent } from 'react'; +import { Suspense } from 'react'; +import { I18nextProvider, initReactI18next } from 'react-i18next'; + +import { AppIdProvider } from '../contexts/AppIdContext'; +import { useAppTranslation } from './useAppTranslation'; + +let i18n: i18next.i18n; + +beforeEach(async () => { + i18n = i18next.createInstance().use(initReactI18next); + + await i18n.init({ + lng: 'en', + resources: { + en: { + 'translation': { + test: 'a quick brown fox', + }, + 'app-test': { + test: 'jumped over the lazy dog', + }, + 'app-test-core': { + test: 'this should not be used', + }, + }, + }, + }); +}); + +it('should work with normal app ID (`test`)', async () => { + const { result } = renderHook(() => useAppTranslation().t('test'), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toBe('jumped over the lazy dog'); +}); + +it('should work with core app ID (`test-core`)', async () => { + const { result } = renderHook(() => useAppTranslation().t('test'), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toBe('a quick brown fox'); +}); + +describe('with suspense', () => { + let i18n: i18next.i18n; + + beforeEach(async () => { + i18n = i18next + .createInstance() + .use({ + type: 'backend', + init: () => undefined, + read: async (language, namespace) => { + await new Promise((resolve) => queueMicrotask(resolve)); + + if (language === 'en' && namespace === 'core') { + return { + test: 'a quick brown fox', + }; + } + + if (language === 'en' && namespace === 'app-test') { + return { + test: 'jumped over the lazy dog', + }; + } + + throw new Error(`Unexpected read request: ${language} ${namespace}`); + }, + } satisfies i18next.BackendModule) + .use(initReactI18next); + + await i18n.init({ + lng: 'en', + defaultNS: 'core', + partialBundledLanguages: true, + react: { + useSuspense: true, + }, + }); + }); + + it('should work with normal app ID (`test`)', async () => { + const FakeFallback: FunctionComponent = jest.fn(() => null); + + const { result, waitForNextUpdate } = renderHook( + () => useAppTranslation().t('test'), + { + wrapper: ({ children }) => ( + + }> + {children} + + + ), + } + ); + + await waitForNextUpdate(); + + expect(result.current).toBe('jumped over the lazy dog'); + expect(FakeFallback).toHaveBeenCalled(); + }); + + it('should work with core app ID (`test-core`)', async () => { + const FakeFallback: FunctionComponent = jest.fn(() => null); + + const { result, waitForNextUpdate } = renderHook( + () => useAppTranslation().t('test'), + { + wrapper: ({ children }) => ( + + }> + {children} + + + ), + } + ); + + await waitForNextUpdate(); + + expect(result.current).toBe('a quick brown fox'); + expect(FakeFallback).toHaveBeenCalled(); + }); +}); diff --git a/packages/fuselage-ui-kit/src/hooks/useAppTranslation.ts b/packages/fuselage-ui-kit/src/hooks/useAppTranslation.ts index c29cf0953386..5925f2fada10 100644 --- a/packages/fuselage-ui-kit/src/hooks/useAppTranslation.ts +++ b/packages/fuselage-ui-kit/src/hooks/useAppTranslation.ts @@ -5,7 +5,7 @@ import { useAppId } from '../contexts/AppIdContext'; export const useAppTranslation = () => { const appId = useAppId(); - const appNs = `app-${appId}`; + const appNs = appId.endsWith(`-core`) ? undefined : `app-${appId}`; useDebugValue(appNs); diff --git a/yarn.lock b/yarn.lock index 8993ef8f84cb..d2a910b75b9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2946,6 +2946,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9": + version: 7.24.4 + resolution: "@babel/runtime@npm:7.24.4" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 2f27d4c0ffac7ae7999ac0385e1106f2a06992a8bdcbf3da06adcac7413863cd08c198c2e4e970041bbea849e17f02e1df18875539b6afba76c781b6b59a07c3 + languageName: node + linkType: hard + "@babel/runtime@npm:~7.22.15": version: 7.22.15 resolution: "@babel/runtime@npm:7.22.15" @@ -8751,6 +8760,8 @@ __metadata: "@storybook/source-loader": ~6.5.16 "@storybook/theming": ~6.5.16 "@tanstack/react-query": ^4.16.1 + "@testing-library/react": ^14.2.2 + "@testing-library/react-hooks": ^8.0.1 "@types/babel__core": ^7.20.3 "@types/babel__preset-env": ^7.9.4 "@types/react": ~17.0.69 @@ -8758,14 +8769,17 @@ __metadata: babel-loader: ~8.2.5 cross-env: ^7.0.3 eslint: ~8.45.0 + i18next: ^23.10.1 + jest: ^29.7.0 normalize.css: ^8.0.1 npm-run-all: ^4.1.5 prettier: ~2.8.8 react-docgen-typescript-plugin: ~1.0.5 react-dom: ^17.0.2 - react-i18next: ~13.2.2 + react-i18next: ^14.1.0 rimraf: ^3.0.2 storybook-dark-mode: ~3.0.1 + ts-jest: ^29.1.2 tslib: ^2.5.3 typescript: ~5.3.3 peerDependencies: @@ -12546,6 +12560,20 @@ __metadata: languageName: node linkType: hard +"@testing-library/react@npm:^14.2.2": + version: 14.2.2 + resolution: "@testing-library/react@npm:14.2.2" + dependencies: + "@babel/runtime": ^7.12.5 + "@testing-library/dom": ^9.0.0 + "@types/react-dom": ^18.0.0 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: cb73df588592d9101429f057eaa6f320fc12524d5eb2acc8a16002c1ee2d9422a49e44841003bba42974c9ae1ced6b134f0d647826eca42ab8f19e4592971b16 + languageName: node + linkType: hard + "@testing-library/user-event@npm:^13.2.1, @testing-library/user-event@npm:~13.5.0": version: 13.5.0 resolution: "@testing-library/user-event@npm:13.5.0" @@ -25592,6 +25620,15 @@ __metadata: languageName: node linkType: hard +"i18next@npm:^23.10.1": + version: 23.10.1 + resolution: "i18next@npm:23.10.1" + dependencies: + "@babel/runtime": ^7.23.2 + checksum: 4aec10ddb0bb841f15b9b023daa59977052bc706ca4e94643b12b17640731862bde596c9797491638f6d9e7f125722ea9b1e87394c7aebbb72f45c20396f79d9 + languageName: node + linkType: hard + "i18next@npm:~21.6.16": version: 21.6.16 resolution: "i18next@npm:21.6.16" @@ -27313,7 +27350,7 @@ __metadata: languageName: node linkType: hard -"jest-cli@npm:^29.5.0, jest-cli@npm:^29.6.4": +"jest-cli@npm:^29.5.0, jest-cli@npm:^29.6.4, jest-cli@npm:^29.7.0": version: 29.7.0 resolution: "jest-cli@npm:29.7.0" dependencies: @@ -27842,6 +27879,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": ^29.7.0 + "@jest/types": ^29.6.3 + import-local: ^3.0.2 + jest-cli: ^29.7.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 17ca8d67504a7dbb1998cf3c3077ec9031ba3eb512da8d71cb91bcabb2b8995c4e4b292b740cb9bf1cbff5ce3e110b3f7c777b0cefb6f41ab05445f248d0ee0b + languageName: node + linkType: hard + "jest@npm:~29.5.0": version: 29.5.0 resolution: "jest@npm:29.5.0" @@ -34903,6 +34959,24 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:^14.1.0": + version: 14.1.0 + resolution: "react-i18next@npm:14.1.0" + dependencies: + "@babel/runtime": ^7.23.9 + html-parse-stringify: ^3.0.1 + peerDependencies: + i18next: ">= 23.2.3" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 96fbc4b0919b9f0c639f9f3eb35eecac528174aa97e3b1af469cfdbff4b34e40ae8969c26c0b737691a5fa9b56bb13093524cfca79b93cbd58f1319530da31b2 + languageName: node + linkType: hard + "react-i18next@npm:~13.2.2": version: 13.2.2 resolution: "react-i18next@npm:13.2.2" From cc56f16de865b1956462bb9c8a49d2537b12b124 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:37:02 -0300 Subject: [PATCH 15/16] chore: remove unused onStartup function (#32149) --- apps/meteor/server/lib/onStartup.ts | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 apps/meteor/server/lib/onStartup.ts diff --git a/apps/meteor/server/lib/onStartup.ts b/apps/meteor/server/lib/onStartup.ts deleted file mode 100644 index 2f449c449f4a..000000000000 --- a/apps/meteor/server/lib/onStartup.ts +++ /dev/null @@ -1,25 +0,0 @@ -type StartupCallback = () => Promise; - -const callbackList: StartupCallback[] = []; -let hasStarted = false; - -export const onStartup = (cb: StartupCallback): void => { - if (hasStarted) { - return Promise.await(cb()); - } - - callbackList.push(cb); -}; - -const runCallbacks = async (): Promise => { - for await (const cb of callbackList) { - await cb(); - } - - callbackList.splice(0, callbackList.length); -}; - -Meteor.startup(() => { - hasStarted = true; - Promise.await(runCallbacks()); -}); From a316c2b420cf471e3db43d8b738f65aea5d60b9b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 8 Apr 2024 16:42:29 -0300 Subject: [PATCH 16/16] chore: bump to 6.8.0 (#32153) --- apps/meteor/app/utils/rocketchat.info | 2 +- apps/meteor/package.json | 2 +- package.json | 2 +- packages/core-typings/package.json | 2 +- packages/rest-typings/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/utils/rocketchat.info b/apps/meteor/app/utils/rocketchat.info index 34642c087e2e..4eb357fe9ee6 100644 --- a/apps/meteor/app/utils/rocketchat.info +++ b/apps/meteor/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "7.0.0-develop" + "version": "6.8.0-develop" } diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 55a4bb113659..77dca03343d9 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/meteor", "description": "The Ultimate Open Source WebChat Platform", - "version": "7.0.0-develop", + "version": "6.8.0-develop", "private": true, "author": { "name": "Rocket.Chat", diff --git a/package.json b/package.json index 4e0b99fa9daa..240e1a9a1e02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket.chat", - "version": "7.0.0-develop", + "version": "6.8.0-develop", "description": "Rocket.Chat Monorepo", "main": "index.js", "private": true, diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index 71aa4da0906d..00db0ed2d621 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/package", "name": "@rocket.chat/core-typings", "private": true, - "version": "7.0.0-develop", + "version": "6.8.0-develop", "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "eslint": "~8.45.0", diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json index 3f880c5b20d3..aed48c665870 100644 --- a/packages/rest-typings/package.json +++ b/packages/rest-typings/package.json @@ -1,7 +1,7 @@ { "name": "@rocket.chat/rest-typings", "private": true, - "version": "7.0.0-develop", + "version": "6.8.0-develop", "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "@types/jest": "~29.5.7",