diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 646c83d64c15..850a879c7a89 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.5.0 +ENV RC_VERSION 4.5.1 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 6d6052bb6dcd..6fb618dfbcc9 100644 --- a/.github/history.json +++ b/.github/history.json @@ -71306,6 +71306,157 @@ "'5.0'" ], "pull_requests": [] + }, + "4.5.1": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24760", + "title": "[FIX] Apple login script being loaded even when Apple Login is disabled.", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.5.1", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "24754", + "title": "Chore: Update Livechat", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24683", + "title": "[FIX] no id of room closer in livechat-close message", + "userLogin": "cuonghuunguyen", + "milestone": "4.5.1", + "contributors": [ + null + ] + }, + { + "pr": "23795", + "title": "[FIX] Reload roomslist after successful deletion of a room from admin panel.", + "userLogin": "Aman-Maheshwari", + "description": "Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`.\r\nThis allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent.\r\n\r\nAlso added a succes toast message after the successful deletion of room.", + "milestone": "4.5.1", + "contributors": [ + "Aman-Maheshwari", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24743", + "title": "[FIX] System messages are sent when adding or removing a group from a team", + "userLogin": "matheusbsilva137", + "description": "- Do not send system messages when adding or removing a new or existing _group_ from a team.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24737", + "title": "[FIX] Typo and placeholder on wrap up call modal", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24680", + "title": "[FIX] Show only available agents on extension association modal", + "userLogin": "KevLehman", + "milestone": "4.5.1", + "contributors": [ + "KevLehman", + "tiagoevanp" + ] + }, + { + "pr": "24607", + "title": "[FIX] VoIP Enable/Disable setting on CallContext/CallProvider Notifications", + "userLogin": "tiagoevanp", + "milestone": "4.5.1", + "contributors": [ + "tiagoevanp", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24677", + "title": "[FIX] Components for user search", + "userLogin": "juliajforesti", + "milestone": "4.5.1", + "contributors": [ + "juliajforesti", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "24657", + "title": "[FIX] Voip Stream Reinitialization Error", + "userLogin": "amolghode1981", + "milestone": "4.5.1", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24696", + "title": "[FIX] Room's message count not being incremented on import", + "userLogin": "matheusbsilva137", + "description": "- Fix rooms' message counter not being incremented on message import.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24674", + "title": "[FIX] Missing username on messages imported from Slack", + "userLogin": "matheusbsilva137", + "description": "- Fix missing sender's username on messages imported from Slack.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24590", + "title": "[FIX] Duplicated 'name' log key", + "userLogin": "sampaiodiego", + "milestone": "4.5.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24661", + "title": "[FIX] Typo in wrap-up term", + "userLogin": "renatobecker", + "milestone": "4.5.1", + "contributors": [ + "renatobecker" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index d89666a4a506..15fa6f07861d 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.5.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.5.1/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 41f386057ee0..92087b132cfc 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.5.0 +version: 4.5.1 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 0326008d089a..7de8158ee519 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,78 @@ +# 4.5.1 +`2022-03-09 · 13 🐛 · 1 🔍 · 11 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `'3.6', '4.0', '4.2', '4.4', '5.0'` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- Apple login script being loaded even when Apple Login is disabled. ([#24760](https://github.com/RocketChat/Rocket.Chat/pull/24760)) + +- Components for user search ([#24677](https://github.com/RocketChat/Rocket.Chat/pull/24677)) + +- Duplicated 'name' log key ([#24590](https://github.com/RocketChat/Rocket.Chat/pull/24590)) + +- Missing username on messages imported from Slack ([#24674](https://github.com/RocketChat/Rocket.Chat/pull/24674)) + + - Fix missing sender's username on messages imported from Slack. + +- no id of room closer in livechat-close message ([#24683](https://github.com/RocketChat/Rocket.Chat/pull/24683)) + +- Reload roomslist after successful deletion of a room from admin panel. ([#23795](https://github.com/RocketChat/Rocket.Chat/pull/23795) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + + Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`. + This allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent. + + Also added a succes toast message after the successful deletion of room. + +- Room's message count not being incremented on import ([#24696](https://github.com/RocketChat/Rocket.Chat/pull/24696)) + + - Fix rooms' message counter not being incremented on message import. + +- Show only available agents on extension association modal ([#24680](https://github.com/RocketChat/Rocket.Chat/pull/24680)) + +- System messages are sent when adding or removing a group from a team ([#24743](https://github.com/RocketChat/Rocket.Chat/pull/24743)) + + - Do not send system messages when adding or removing a new or existing _group_ from a team. + +- Typo and placeholder on wrap up call modal ([#24737](https://github.com/RocketChat/Rocket.Chat/pull/24737)) + +- Typo in wrap-up term ([#24661](https://github.com/RocketChat/Rocket.Chat/pull/24661)) + +- VoIP Enable/Disable setting on CallContext/CallProvider Notifications ([#24607](https://github.com/RocketChat/Rocket.Chat/pull/24607)) + +- Voip Stream Reinitialization Error ([#24657](https://github.com/RocketChat/Rocket.Chat/pull/24657)) + +
+🔍 Minor changes + + +- Chore: Update Livechat ([#24754](https://github.com/RocketChat/Rocket.Chat/pull/24754)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@amolghode1981](https://github.com/amolghode1981) +- [@juliajforesti](https://github.com/juliajforesti) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) + # 4.5.0 `2022-02-28 · 3 🎉 · 15 🚀 · 19 🐛 · 72 🔍 · 30 👩‍💻👨‍💻` diff --git a/app/api/server/v1/voip/omnichannel.ts b/app/api/server/v1/voip/omnichannel.ts index 163f1f19b5e8..a84831de87c1 100644 --- a/app/api/server/v1/voip/omnichannel.ts +++ b/app/api/server/v1/voip/omnichannel.ts @@ -72,6 +72,9 @@ API.v1.addRoute( let user: IUser | null = null; if (!isUserAndExtensionParams(this.bodyParams)) { + if (!this.bodyParams.username) { + return API.v1.notFound(); + } user = await Users.findOneByAgentUsername(this.bodyParams.username, { projection: { _id: 1, @@ -79,6 +82,9 @@ API.v1.addRoute( }, }); } else { + if (!this.bodyParams.userId) { + return API.v1.notFound(); + } user = await Users.findOneAgentById(this.bodyParams.userId, { projection: { _id: 1, @@ -167,33 +173,22 @@ API.v1.addRoute( if (!extensions) { return API.v1.failure('Error in allocated extensions'); } - return API.v1.success({ extensions }); + return API.v1.success({ extensions: extensions.map((e) => e.extension) }); } case 'available': { let user: IUser | null = null; if (!isUserIdndTypeParams(this.queryParams)) { user = await Users.findOneByAgentUsername(this.queryParams.username, { - projection: { _id: 1 }, + projection: { _id: 1, extension: 1 }, }); } else { user = await Users.findOneAgentById(this.queryParams.userId, { - projection: { _id: 1 }, + projection: { _id: 1, extension: 1 }, }); } - if (!user) { - return API.v1.notFound('User not found'); - } - - const extension = await Users.getVoipExtensionByUserId(user._id, { - projection: { - _id: 1, - username: 1, - extension: 1, - }, - }); const freeExt = await LivechatVoip.getFreeExtensions(); - const extensions = extension ? [extension.extension, ...freeExt] : freeExt; + const extensions = user?.extension ? [user.extension, ...freeExt] : freeExt; return API.v1.success({ extensions }); } default: @@ -221,3 +216,24 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'omnichannel/agents/available', + { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] }, + { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { text, includeExtension = '' } = this.queryParams; + + const { agents, total } = await LivechatVoip.getAvailableAgents(includeExtension, text, count, offset, sort); + + return API.v1.success({ + agents, + offset, + count, + total, + }); + }, + }, +); diff --git a/app/apps/server/bridges/livechat.ts b/app/apps/server/bridges/livechat.ts index 3c79c709fbb7..3c58c649d55e 100644 --- a/app/apps/server/bridges/livechat.ts +++ b/app/apps/server/bridges/livechat.ts @@ -111,7 +111,7 @@ export class AppLivechatBridge extends LivechatBridge { protected async closeRoom(room: ILivechatRoom, comment: string, closer: IUser | undefined, appId: string): Promise { this.orch.debugLog(`The App ${appId} is closing a livechat room.`); - const user = closer && this.orch.getConverters()?.get('users').convertById(closer.id); + const user = closer && this.orch.getConverters()?.get('users').convertToRocketChat(closer); const visitor = this.orch.getConverters()?.get('visitors').convertAppVisitor(room.visitor); const closeData: any = { diff --git a/app/importer/server/classes/ImportDataConverter.ts b/app/importer/server/classes/ImportDataConverter.ts index 95d79f36df46..4bfa7ac5b46f 100644 --- a/app/importer/server/classes/ImportDataConverter.ts +++ b/app/importer/server/classes/ImportDataConverter.ts @@ -263,7 +263,7 @@ export class ImportDataConverter { } if (userData.importIds.length) { - this.addUserToCache(userData.importIds[0], existingUser._id, existingUser.username); + this.addUserToCache(userData.importIds[0], existingUser._id, existingUser.username || userData.username); } } diff --git a/app/lib/server/functions/createRoom.js b/app/lib/server/functions/createRoom.js index 96cd49c579d0..50719f2553c2 100644 --- a/app/lib/server/functions/createRoom.js +++ b/app/lib/server/functions/createRoom.js @@ -131,12 +131,11 @@ export const createRoom = function (type, name, owner, members = [], readOnly, { addUserRoles(owner._id, ['owner'], room._id); - if (room.teamId) { - const team = Promise.await(Team.getOneById(room.teamId)); - Messages.createUserAddRoomToTeamWithRoomIdAndUser(team.roomId, room.name, owner); - } - if (type === 'c') { + if (room.teamId) { + const team = Promise.await(Team.getOneById(room.teamId)); + Messages.createUserAddRoomToTeamWithRoomIdAndUser(team.roomId, room.name, owner); + } Meteor.defer(() => { callbacks.run('afterCreateChannel', owner, room); }); diff --git a/app/lib/server/functions/insertMessage.js b/app/lib/server/functions/insertMessage.js index a9c237b78858..13595390ca86 100644 --- a/app/lib/server/functions/insertMessage.js +++ b/app/lib/server/functions/insertMessage.js @@ -1,4 +1,4 @@ -import { Messages } from '../../../models/server'; +import { Messages, Rooms } from '../../../models/server'; import { validateMessage, prepareMessageObject } from './sendMessage'; import { parseUrlsInMessage } from './parseUrlsInMessage'; @@ -14,6 +14,7 @@ export const insertMessage = function (user, message, rid, upsert = false) { if (message._id && upsert) { const { _id } = message; delete message._id; + const existingMessage = Messages.findOneById(_id); Messages.upsert( { _id, @@ -21,9 +22,13 @@ export const insertMessage = function (user, message, rid, upsert = false) { }, message, ); + if (!existingMessage) { + Rooms.incMsgCountById(rid, 1); + } message._id = _id; } else { message._id = Messages.insert(message); + Rooms.incMsgCountById(rid, 1); } return message; diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index d4e284252a11..03748aeaa323 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -967,4 +967,22 @@ export class UsersRaw extends BaseRaw { }; return this.update(query, update); } + + getAvailableAgentsIncludingExt(includeExt, text, options) { + const query = { + roles: { $in: ['livechat-agent', 'livechat-manager', 'livechat-monitor'] }, + $and: [ + { $or: [...(includeExt ? [{ extension: includeExt }] : []), { extension: { $exists: false } }] }, + ...(text && text.trim() + ? [ + { + $or: [{ username: escapeRegExp(text) }, { name: escapeRegExp(text) }], + }, + ] + : []), + ], + }; + + return this.find(query, options); + } } diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 5a4ca2b3d08a..a14d9200f10d 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.5.0" + "version": "4.5.1" } diff --git a/client/components/AutoCompleteAgentWithoutExtension.tsx b/client/components/AutoCompleteAgentWithoutExtension.tsx new file mode 100644 index 000000000000..079a68192039 --- /dev/null +++ b/client/components/AutoCompleteAgentWithoutExtension.tsx @@ -0,0 +1,69 @@ +import { PaginatedSelectFiltered } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import React, { FC, memo, useMemo, useState } from 'react'; + +import { ILivechatAgent } from '../../definition/ILivechatAgent'; +import { useRecordList } from '../hooks/lists/useRecordList'; +import { AsyncStatePhase } from '../lib/asyncState'; +import { useAvailableAgentsList } from './Omnichannel/hooks/useAvailableAgentsList'; + +type AutoCompleteAgentProps = { + onChange: (value: string) => void; + empty: boolean; + haveAll?: boolean; + value?: string; + currentExtension?: string; +}; + +const AutoCompleteAgentWithoutExtension: FC = (props) => { + const { value, currentExtension, onChange = (): void => undefined, haveAll = false } = props; + const [agentsFilter, setAgentsFilter] = useState(''); + + const debouncedAgentsFilter = useDebouncedValue(agentsFilter as string, 500); + + const { itemsList: AgentsList, loadMoreItems: loadMoreAgents } = useAvailableAgentsList( + useMemo( + () => ({ text: debouncedAgentsFilter, includeExtension: currentExtension, haveAll }), + [currentExtension, debouncedAgentsFilter, haveAll], + ), + ); + const { phase: agentsPhase, items: agentsItems, itemCount: agentsTotal } = useRecordList(AgentsList); + const sortedByName = agentsItems + .sort((a, b) => { + if (value === 'all') { + return -1; + } + + if (a?.username?.localeCompare(b?.username || '')) { + return 1; + } + if (b?.username?.localeCompare(b?.username || '')) { + return -1; + } + + return 0; + }) + .map((agent): ILivechatAgent & { label: string; value: string } => ({ + ...agent, + label: agent?.username || '', + value: agent?.username || '', + })); + + return ( + { + setAgentsFilter(value); + }} + options={sortedByName} + endReached={ + agentsPhase === AsyncStatePhase.LOADING ? (): void => undefined : (start): void => loadMoreAgents(start, Math.min(50, agentsTotal)) + } + /> + ); +}; + +export default memo(AutoCompleteAgentWithoutExtension); diff --git a/client/components/Omnichannel/hooks/useAvailableAgentsList.ts b/client/components/Omnichannel/hooks/useAvailableAgentsList.ts new file mode 100644 index 000000000000..81c16785c65d --- /dev/null +++ b/client/components/Omnichannel/hooks/useAvailableAgentsList.ts @@ -0,0 +1,65 @@ +import { useCallback, useState } from 'react'; + +import { ILivechatAgent } from '../../../../definition/ILivechatAgent'; +import { useEndpoint } from '../../../contexts/ServerContext'; +import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList'; +import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; +import { RecordList } from '../../../lib/lists/RecordList'; + +type AgentsListOptions = { + text: string; + includeExtension?: string; +}; + +export const useAvailableAgentsList = ( + options: AgentsListOptions, +): { + itemsList: RecordList; + initialItemCount: number; + reload: () => void; + loadMoreItems: (start: number, end: number) => void; +} => { + const [itemsList, setItemsList] = useState(() => new RecordList()); + const reload = useCallback(() => setItemsList(new RecordList()), []); + const endpoint = 'omnichannel/agents/available'; + + const getAgents = useEndpoint('GET', endpoint); + + useComponentDidUpdate(() => { + options && reload(); + }, [options, reload]); + + const fetchData = useCallback( + async (start, end) => { + const { agents, total } = await getAgents({ + ...(options.text && { text: options.text }), + ...(options.includeExtension && { includeExtension: options.includeExtension }), + offset: start, + count: end + start, + sort: `{ "name": 1 }`, + }); + + const items = agents.map((agent: any) => { + agent._updatedAt = new Date(agent._updatedAt); + agent.label = agent.username; + agent.value = agent._id; + return agent; + }); + + return { + items, + itemCount: total, + }; + }, + [getAgents, options.includeExtension, options.text], + ); + + const { loadMoreItems, initialItemCount } = useScrollableRecordList(itemsList, fetchData, 25); + + return { + reload, + itemsList, + loadMoreItems, + initialItemCount, + }; +}; diff --git a/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js b/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js index c1358530c191..482fd6e39fbc 100644 --- a/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js +++ b/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js @@ -1,41 +1,59 @@ import { MultiSelectFiltered, Box, Option, OptionAvatar, OptionContent, OptionDescription, Chip, CheckBox } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import React, { memo, useMemo, useState } from 'react'; +import React, { memo, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointData } from '../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../lib/asyncState'; import UserAvatar from '../avatar/UserAvatar'; const query = (term = '') => ({ selector: JSON.stringify({ term }) }); -const UserAutoCompleteMultiple = (props) => { +const UserAutoCompleteMultiple = ({ valueIsId = false, ...props }) => { + const t = useTranslation(); const [filter, setFilter] = useState(''); + const [labelData, setLabelData] = useState({}); const debouncedFilter = useDebouncedValue(filter, 1000); - const { value: data } = useEndpointData( + const { value: data, phase } = useEndpointData( 'users.autocomplete', useMemo(() => query(debouncedFilter), [debouncedFilter]), ); - const options = useMemo(() => (data && data.items.map((user) => [user.username, user.name])) || [], [data]); - - const renderItem = ({ value, label, selected, ...props }) => ( - + const options = useMemo( + () => (data && data.items.map((user) => [valueIsId ? user._id : user.username, user.name])) || [], + [data, valueIsId], ); - const renderSelected = ({ value, onMouseDown }) => ( - - - - {value} - - - ); + useEffect(() => { + const newLabelData = Object.fromEntries((data && data.items.map((user) => [user._id, user.username])) || []); + setLabelData((labelData) => ({ ...labelData, ...newLabelData })); + }, [data]); + + const renderItem = ({ value, label, selected, ...props }) => { + const username = valueIsId ? labelData[value] : value; + return ( + + ); + }; + + const renderSelected = ({ value, onMouseDown }) => { + const username = valueIsId ? labelData[value] : value; + return ( + + + + {username} + + + ); + }; return ( { renderSelected={renderSelected} renderItem={renderItem} addonIcon='magnifier' + customEmpty={phase === AsyncStatePhase.LOADING ? t('Loading') : t('None')} // TODO: add proper empty state /> ); }; diff --git a/client/components/voip/modal/WrapUpCallModal.tsx b/client/components/voip/modal/WrapUpCallModal.tsx index 0ac7e357ee8d..1dbd42e23579 100644 --- a/client/components/voip/modal/WrapUpCallModal.tsx +++ b/client/components/voip/modal/WrapUpCallModal.tsx @@ -44,14 +44,14 @@ export const WrapUpCallModal = (): ReactElement => { return ( - {t('Wrap_Up_the_Call')} + {t('Wrap_up_the_call')} {t('Notes')} - + {t('These_notes_will_be_available_in_the_call_summary')} diff --git a/client/lib/voip/Stream.ts b/client/lib/voip/Stream.ts index d8d33b95927f..5cb078d58cb2 100644 --- a/client/lib/voip/Stream.ts +++ b/client/lib/voip/Stream.ts @@ -61,7 +61,8 @@ export default class Stream { if (this.renderingMediaElement) { // Someone already has setup the stream and initializing it once again // Clear the existing stream object - this.renderingMediaElement.srcObject == null; + this.renderingMediaElement.pause(); + this.renderingMediaElement.srcObject = null; } this.renderingMediaElement = rmElement; } diff --git a/client/providers/CallProvider/CallProvider.tsx b/client/providers/CallProvider/CallProvider.tsx index 1b3307b2a1ae..14ec7d084bc6 100644 --- a/client/providers/CallProvider/CallProvider.tsx +++ b/client/providers/CallProvider/CallProvider.tsx @@ -4,7 +4,6 @@ import { createPortal } from 'react-dom'; import { OutgoingByeRequest } from 'sip.js/lib/core'; import { CustomSounds } from '../../../app/custom-sounds/client'; -import { Notifications } from '../../../app/notifications/client'; import { getUserPreference } from '../../../app/utils/client'; import { IVoipRoom } from '../../../definition/IRoom'; import { IUser } from '../../../definition/IUser'; @@ -12,7 +11,7 @@ import { WrapUpCallModal } from '../../components/voip/modal/WrapUpCallModal'; import { CallContext, CallContextValue } from '../../contexts/CallContext'; import { useSetModal } from '../../contexts/ModalContext'; import { useRoute } from '../../contexts/RouterContext'; -import { useEndpoint } from '../../contexts/ServerContext'; +import { useEndpoint, useStream } from '../../contexts/ServerContext'; import { useSetting } from '../../contexts/SettingsContext'; import { useUser } from '../../contexts/UserContext'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; @@ -33,8 +32,8 @@ const stopRingback = (): void => { export const CallProvider: FC = ({ children }) => { const voipEnabled = useSetting('VoIP_Enabled'); + const subscribeToNotifyUser = useStream('notify-user'); - // TODO: Test Settings and return false if its disabled (based on the settings) const result = useVoipClient(); const user = useUser(); @@ -52,114 +51,131 @@ export const CallProvider: FC = ({ children }) => { setModal(); }, [setModal]); - const handleAgentConnected = useCallback( - (queue: { queuename: string; queuedcalls: string; waittimeinqueue: string }): void => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.callPickedup(queue); - setQueueCounter(queueAggregator.getCallWaitingCount().toString()); - } - }, - [result], - ); + useEffect(() => { + if (!voipEnabled || !user) { + return; + } - const handleAgentCalled = useCallback( - (queueInfo: { queuename: string; callerId: { id: string; name: string } }): void => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.callRinging(queueInfo); - } - }, - [result], - ); + if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { + return; + } - const handleMemberAdded = useCallback( - (queue: { queuename: string; queuedcalls: string }): void => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.memberAdded(queue); - setQueueCounter(queueAggregator.getCallWaitingCount().toString()); - } - }, - [result], - ); + const queueAggregator = result.voipClient.getAggregator(); + if (!queueAggregator) { + return; + } - const handleMemberRemoved = useCallback( - (queue: { queuename: string; queuedcalls: string }): void => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.memberRemoved(queue); - setQueueCounter(queueAggregator.getCallWaitingCount().toString()); - } - }, - [result], - ); + const handleQueueJoined = async (joiningDetails: { + queuename: string; + callerid: { id: string }; + queuedcalls: string; + }): Promise => { + queueAggregator.queueJoined(joiningDetails); + setQueueCounter(queueAggregator.getCallWaitingCount().toString()); + }; - const handleCallAbandon = useCallback( - (queue: { queuename: string; queuedcallafterabandon: string }): void => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.queueAbandoned(queue); - setQueueCounter(queueAggregator.getCallWaitingCount().toString()); - } - }, - [result], - ); + return subscribeToNotifyUser(`${user._id}/callerjoined`, handleQueueJoined); + }, [result, subscribeToNotifyUser, user, voipEnabled]); - const handleQueueJoined = useCallback( - async (joiningDetails: { queuename: string; callerid: { id: string }; queuedcalls: string }): Promise => { - if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { - return; - } - const queueAggregator = result.voipClient.getAggregator(); - if (queueAggregator) { - queueAggregator.queueJoined(joiningDetails); - setQueueCounter(queueAggregator.getCallWaitingCount().toString()); - } - }, - [result], - ); + useEffect(() => { + if (!voipEnabled || !user) { + return; + } - const handleCallHangup = useCallback( - (_event: { roomId: string }) => { - openWrapUpModal(); - }, - [openWrapUpModal], - ); + if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { + return; + } + + const queueAggregator = result.voipClient.getAggregator(); + if (!queueAggregator) { + return; + } + + const handleAgentConnected = (queue: { queuename: string; queuedcalls: string; waittimeinqueue: string }): void => { + queueAggregator.callPickedup(queue); + setQueueCounter(queueAggregator.getCallWaitingCount().toString()); + }; + + return subscribeToNotifyUser(`${user._id}/agentconnected`, handleAgentConnected); + }, [result, subscribeToNotifyUser, user, voipEnabled]); useEffect(() => { - Notifications.onUser('callerjoined', handleQueueJoined); - Notifications.onUser('agentcalled', handleAgentCalled); - Notifications.onUser('agentconnected', handleAgentConnected); - Notifications.onUser('queuememberadded', handleMemberAdded); - Notifications.onUser('queuememberremoved', handleMemberRemoved); - Notifications.onUser('callabandoned', handleCallAbandon); - Notifications.onUser('call.callerhangup', handleCallHangup); - Notifications.onUser('call.callerhangup', handleCallHangup); - }, [ - handleQueueJoined, - handleMemberAdded, - handleMemberRemoved, - handleCallAbandon, - handleAgentConnected, - handleCallHangup, - handleAgentCalled, - ]); + if (!voipEnabled || !user) { + return; + } + + if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { + return; + } + + const queueAggregator = result.voipClient.getAggregator(); + if (!queueAggregator) { + return; + } + + const handleMemberAdded = (queue: { queuename: string; queuedcalls: string }): void => { + queueAggregator.memberAdded(queue); + setQueueCounter(queueAggregator.getCallWaitingCount().toString()); + }; + + return subscribeToNotifyUser(`${user._id}/queuememberadded`, handleMemberAdded); + }, [result, subscribeToNotifyUser, user, voipEnabled]); + + useEffect(() => { + if (!voipEnabled || !user) { + return; + } + + if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { + return; + } + + const queueAggregator = result.voipClient.getAggregator(); + if (!queueAggregator) { + return; + } + + const handleMemberRemoved = (queue: { queuename: string; queuedcalls: string }): void => { + queueAggregator.memberRemoved(queue); + setQueueCounter(queueAggregator.getCallWaitingCount().toString()); + }; + + return subscribeToNotifyUser(`${user._id}/queuememberremoved`, handleMemberRemoved); + }, [result, subscribeToNotifyUser, user, voipEnabled]); + + useEffect(() => { + if (!voipEnabled || !user) { + return; + } + + if (isUseVoipClientResultError(result) || isUseVoipClientResultLoading(result)) { + return; + } + + const queueAggregator = result.voipClient.getAggregator(); + if (!queueAggregator) { + return; + } + + const handleCallAbandon = (queue: { queuename: string; queuedcallafterabandon: string }): void => { + queueAggregator.queueAbandoned(queue); + setQueueCounter(queueAggregator.getCallWaitingCount().toString()); + }; + + return subscribeToNotifyUser(`${user._id}/callabandoned`, handleCallAbandon); + }, [result, subscribeToNotifyUser, user, voipEnabled]); + + useEffect(() => { + if (!voipEnabled || !user) { + return; + } + + const handleCallHangup = (_event: { roomId: string }): void => { + openWrapUpModal(); + }; + + return subscribeToNotifyUser(`${user._id}/call.callerhangup`, handleCallHangup); + }, [openWrapUpModal, result, subscribeToNotifyUser, user, voipEnabled]); useEffect(() => { if (isUseVoipClientResultError(result)) { @@ -169,7 +185,8 @@ export const CallProvider: FC = ({ children }) => { if (isUseVoipClientResultLoading(result)) { return; } - /** + + /* * This code may need a revisit when we handle callinqueue differently. * Check clickup taks for more details * https://app.clickup.com/t/22hy1k4 @@ -231,6 +248,7 @@ export const CallProvider: FC = ({ children }) => { error: result.error, }; } + if (isUseVoipClientResultLoading(result)) { return { enabled: true, @@ -292,6 +310,7 @@ export const CallProvider: FC = ({ children }) => { openWrapUpModal, }; }, [queueCounter, voipEnabled, homeRoute, openWrapUpModal, result, roomInfo, user, visitorEndpoint, voipCloseRoomEndpoint, voipEndpoint]); + return ( {children} diff --git a/client/providers/CallProvider/hooks/useVoipClient.ts b/client/providers/CallProvider/hooks/useVoipClient.ts index 44118836441d..7c3dd350c6cb 100644 --- a/client/providers/CallProvider/hooks/useVoipClient.ts +++ b/client/providers/CallProvider/hooks/useVoipClient.ts @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'; import { IRegistrationInfo } from '../../../../definition/voip/IRegistrationInfo'; import { WorkflowTypes } from '../../../../definition/voip/WorkflowTypes'; import { useEndpoint } from '../../../contexts/ServerContext'; +import { useSetting } from '../../../contexts/SettingsContext'; import { useUser } from '../../../contexts/UserContext'; import { SimpleVoipUser } from '../../../lib/voip/SimpleVoipUser'; import { VoIPUser } from '../../../lib/voip/VoIPUser'; @@ -28,6 +29,7 @@ export const isUseVoipClientResultLoading = (result: UseVoipClientResult): resul const isSignedResponse = (data: any): data is { result: string } => typeof data?.result === 'string'; export const useVoipClient = (): UseVoipClientResult => { + const voipEnabled = useSetting('VoIP_Enabled'); const registrationInfo = useEndpoint('GET', 'connector.extension.getRegistrationInfoByUserId'); const membership = useEndpoint('GET', 'voip/queues.getMembershipSubscription'); const user = useUser(); @@ -36,10 +38,11 @@ export const useVoipClient = (): UseVoipClientResult => { const [result, setResult] = useSafely(useState({})); useEffect(() => { - if (!user || !user?._id) { + if (!user || !user?._id || !voipEnabled) { setResult({}); return; } + registrationInfo({ id: user._id }).then( (data) => { let parsedData: IRegistrationInfo; @@ -79,7 +82,7 @@ export const useVoipClient = (): UseVoipClientResult => { // client?.disconnect(); // TODO how to close the client? before creating a new one? }; - }, [user, iceServers, registrationInfo, setResult, membership]); + }, [user, iceServers, registrationInfo, setResult, membership, voipEnabled]); return result; }; diff --git a/client/views/admin/rooms/EditRoom.js b/client/views/admin/rooms/EditRoom.js index 80cd07267147..f12441449902 100644 --- a/client/views/admin/rooms/EditRoom.js +++ b/client/views/admin/rooms/EditRoom.js @@ -1,4 +1,4 @@ -import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, Callout, TextAreaInput } from '@rocket.chat/fuselage'; +import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, TextAreaInput } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useState, useMemo } from 'react'; @@ -8,11 +8,14 @@ import VerticalBar from '../../../components/VerticalBar'; import RoomAvatarEditor from '../../../components/avatar/RoomAvatarEditor'; import { usePermission } from '../../../contexts/AuthorizationContext'; import { useSetModal } from '../../../contexts/ModalContext'; -import { useMethod } from '../../../contexts/ServerContext'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useEndpoint, useMethod } from '../../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; import { useTranslation } from '../../../contexts/TranslationContext'; import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental'; import { useForm } from '../../../hooks/useForm'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import DeleteTeamModal from '../../teams/contextualBar/info/Delete/DeleteTeamModal'; const getInitialValues = (room) => ({ roomName: room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, { type: room.t, ...room }), @@ -28,13 +31,13 @@ const getInitialValues = (room) => ({ roomAvatar: undefined, }); -function EditRoom({ room, onChange }) { +function EditRoom({ room, onChange, onDelete }) { const t = useTranslation(); - const [deleted, setDeleted] = useState(false); + const [deleting, setDeleting] = useState(false); const setModal = useSetModal(); - + const dispatchToastMessage = useToastMessageDispatch(); const { values, handlers, hasUnsavedChanges, reset } = useForm(getInitialValues(room)); const [canViewName, canViewTopic, canViewAnnouncement, canViewArchived, canViewDescription, canViewType, canViewReadOnly] = @@ -81,6 +84,8 @@ function EditRoom({ room, onChange }) { const changeArchivation = archived !== !!room.archived; + const roomsRoute = useRoute('admin-rooms'); + const canDelete = usePermission(`delete-${room.t}`); const archiveSelector = room.archived ? 'unarchive' : 'archive'; @@ -115,18 +120,57 @@ function EditRoom({ room, onChange }) { handleRoomType(roomType === 'p' ? 'c' : 'p'); }); - const deleteRoom = useMethod('eraseRoom'); + const eraseRoom = useMethod('eraseRoom'); + const deleteTeam = useEndpoint('POST', 'teams.delete'); const handleDelete = useMutableCallback(() => { - const onCancel = () => setModal(undefined); - const onConfirm = async () => { - await deleteRoom(room._id); - onCancel(); - setDeleted(true); - }; + if (room.teamMain) { + setModal( + { + const roomsToRemove = Array.isArray(deletedRooms) && deletedRooms.length > 0 ? deletedRooms : []; + + try { + setDeleting(true); + setModal(null); + await deleteTeam({ teamId: room.teamId, ...(roomsToRemove.length && { roomsToRemove }) }); + dispatchToastMessage({ type: 'success', message: t('Team_has_been_deleted') }); + roomsRoute.push({}); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + setDeleting(false); + } finally { + onDelete(); + } + }} + onCancel={() => setModal(null)} + teamId={room.teamId} + />, + ); + + return; + } setModal( - + { + try { + setDeleting(true); + setModal(null); + await eraseRoom(room._id); + dispatchToastMessage({ type: 'success', message: t('Room_has_been_deleted') }); + roomsRoute.push({}); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + setDeleting(false); + } finally { + onDelete(); + } + }} + onCancel={() => setModal(null)} + confirmText={t('Yes_delete_it')} + > {t('Delete_Room_Warning')} , ); @@ -134,7 +178,6 @@ function EditRoom({ room, onChange }) { return ( e.preventDefault())}> - {deleted && } {room.t !== 'd' && ( @@ -143,7 +186,7 @@ function EditRoom({ room, onChange }) { {t('Name')} - + {room.t !== 'd' && ( @@ -158,7 +201,7 @@ function EditRoom({ room, onChange }) { {t('Description')} - + )} @@ -166,7 +209,7 @@ function EditRoom({ room, onChange }) { {t('Announcement')} - + )} @@ -174,7 +217,7 @@ function EditRoom({ room, onChange }) { {t('Topic')} - + )} @@ -182,7 +225,7 @@ function EditRoom({ room, onChange }) { {t('Private')} - + {t('Just_invited_people_can_access_this_channel')} @@ -192,7 +235,7 @@ function EditRoom({ room, onChange }) { {t('Read_only')} - + {t('Only_authorized_users_can_write_new_messages')} @@ -203,7 +246,7 @@ function EditRoom({ room, onChange }) { {t('Room_archivation_state_true')} - + @@ -214,7 +257,7 @@ function EditRoom({ room, onChange }) { {t('Default')} - + @@ -222,7 +265,7 @@ function EditRoom({ room, onChange }) { {t('Favorite')} - + @@ -230,7 +273,7 @@ function EditRoom({ room, onChange }) { {t('Featured')} - + @@ -238,10 +281,10 @@ function EditRoom({ room, onChange }) { - - @@ -250,7 +293,7 @@ function EditRoom({ room, onChange }) { - diff --git a/client/views/admin/rooms/EditRoomContextBar.js b/client/views/admin/rooms/EditRoomContextBar.js index c0aa0a12224e..3e0318e65cea 100644 --- a/client/views/admin/rooms/EditRoomContextBar.js +++ b/client/views/admin/rooms/EditRoomContextBar.js @@ -4,9 +4,9 @@ import NotAuthorizedPage from '../../../components/NotAuthorizedPage'; import { usePermission } from '../../../contexts/AuthorizationContext'; import EditRoomWithData from './EditRoomWithData'; -function EditRoomContextBar({ rid }) { +function EditRoomContextBar({ rid, onReload }) { const canViewRoomAdministration = usePermission('view-room-administration'); - return canViewRoomAdministration ? : ; + return canViewRoomAdministration ? : ; } export default EditRoomContextBar; diff --git a/client/views/admin/rooms/EditRoomWithData.js b/client/views/admin/rooms/EditRoomWithData.js index ee815fe1a27b..cedf9bbdbe43 100644 --- a/client/views/admin/rooms/EditRoomWithData.js +++ b/client/views/admin/rooms/EditRoomWithData.js @@ -5,7 +5,7 @@ import { AsyncStatePhase } from '../../../hooks/useAsyncState'; import { useEndpointData } from '../../../hooks/useEndpointData'; import EditRoom from './EditRoom'; -function EditRoomWithData({ rid }) { +function EditRoomWithData({ rid, onReload }) { const { value: data = {}, phase: state, @@ -33,7 +33,16 @@ function EditRoomWithData({ rid }) { return error.message; } - return ; + const handleChange = () => { + reload(); + onReload(); + }; + + const handleDelete = () => { + onReload(); + }; + + return ; } export default EditRoomWithData; diff --git a/client/views/admin/rooms/RoomsPage.js b/client/views/admin/rooms/RoomsPage.js index 82cde4e34dae..179417e8c842 100644 --- a/client/views/admin/rooms/RoomsPage.js +++ b/client/views/admin/rooms/RoomsPage.js @@ -1,12 +1,28 @@ -import React from 'react'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import React, { useState, useMemo } from 'react'; import Page from '../../../components/Page'; import VerticalBar from '../../../components/VerticalBar'; import { useRouteParameter, useRoute } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointData } from '../../../hooks/useEndpointData'; import EditRoomContextBar from './EditRoomContextBar'; import RoomsTable from './RoomsTable'; +export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams']; + +const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) => + useMemo( + () => ({ + filter: text || '', + types, + sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), + ...(itemsPerPage && { count: itemsPerPage }), + ...(current && { offset: current }), + }), + [text, types, itemsPerPage, current, column, direction], + ); + export function RoomsPage() { const t = useTranslation(); @@ -19,12 +35,27 @@ export function RoomsPage() { roomsRoute.push({}); }; + const [params, setParams] = useState({ + text: '', + types: DEFAULT_TYPES, + current: 0, + itemsPerPage: 25, + }); + const [sort, setSort] = useState(['name', 'asc']); + + const debouncedParams = useDebouncedValue(params, 500); + const debouncedSort = useDebouncedValue(sort, 500); + + const query = useQuery(debouncedParams, debouncedSort); + + const endpointData = useEndpointData('rooms.adminRooms', query); + return ( - + {context && ( @@ -34,7 +65,7 @@ export function RoomsPage() { - + )} diff --git a/client/views/admin/rooms/RoomsTable.js b/client/views/admin/rooms/RoomsTable.js index c9efd973f6d1..a75f0a2521db 100644 --- a/client/views/admin/rooms/RoomsTable.js +++ b/client/views/admin/rooms/RoomsTable.js @@ -1,20 +1,17 @@ import { Box, Table, Icon } from '@rocket.chat/fuselage'; -import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useCallback, useState } from 'react'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import React, { useMemo, useCallback } from 'react'; import GenericTable from '../../../components/GenericTable'; import RoomAvatar from '../../../components/avatar/RoomAvatar'; import { useRoute } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; -import { useEndpointData } from '../../../hooks/useEndpointData'; import { AsyncStatePhase } from '../../../lib/asyncState'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import FilterByTypeAndText from './FilterByTypeAndText'; const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }; -export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams']; - export const roomTypeI18nMap = { l: 'Omnichannel', c: 'Channel', @@ -30,18 +27,6 @@ const getRoomType = (room) => { return roomTypeI18nMap[room.t]; }; -const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) => - useMemo( - () => ({ - filter: text || '', - types, - sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), - ...(itemsPerPage && { count: itemsPerPage }), - ...(current && { offset: current }), - }), - [text, types, itemsPerPage, current, column, direction], - ); - const getRoomDisplayName = (room) => (room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, room)); const useDisplayData = (asyncState, sort) => @@ -66,29 +51,14 @@ const useDisplayData = (asyncState, sort) => return value.rooms; }, [asyncState, sort]); -function RoomsTable() { +function RoomsTable({ endpointData, params, onChangeParams, sort, onChangeSort }) { const t = useTranslation(); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const [params, setParams] = useState({ - text: '', - types: DEFAULT_TYPES, - current: 0, - itemsPerPage: 25, - }); - const [sort, setSort] = useState(['name', 'asc']); - const routeName = 'admin-rooms'; - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue(sort, 500); - - const query = useQuery(debouncedParams, debouncedSort); - - const asyncState = useEndpointData('rooms.adminRooms', query); - - const { value: data = {} } = asyncState; + const { value: data = {} } = endpointData; const router = useRoute(routeName); @@ -106,15 +76,15 @@ function RoomsTable() { const [sortBy, sortDirection] = sort; if (sortBy === id) { - setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); + onChangeSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); return; } - setSort([id, 'asc']); + onChangeSort([id, 'asc']); }, - [sort], + [sort, onChangeSort], ); - const displayData = useDisplayData(asyncState, sort); + const displayData = useDisplayData(endpointData, sort); const header = useMemo( () => @@ -218,7 +188,7 @@ function RoomsTable() { renderRow={renderRow} results={displayData} total={data.total} - setParams={setParams} + setParams={onChangeParams} params={params} renderFilter={({ onChange, ...props }) => } /> diff --git a/client/views/admin/settings/groups/voip/AssignAgentModal.tsx b/client/views/admin/settings/groups/voip/AssignAgentModal.tsx index d36f01f51f92..a74425b2e266 100644 --- a/client/views/admin/settings/groups/voip/AssignAgentModal.tsx +++ b/client/views/admin/settings/groups/voip/AssignAgentModal.tsx @@ -2,8 +2,9 @@ import { Button, ButtonGroup, Modal, Select, Field, FieldGroup } from '@rocket.c import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { FC, useState, useMemo } from 'react'; -import AutoCompleteAgent from '../../../../../components/AutoCompleteAgent'; +import AutoCompleteAgentWithoutExtension from '../../../../../components/AutoCompleteAgentWithoutExtension'; import { useEndpoint } from '../../../../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../../../../contexts/ToastMessagesContext'; import { useTranslation } from '../../../../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; @@ -16,6 +17,7 @@ type AssignAgentModalParams = { const AssignAgentModal: FC = ({ existingExtension, closeModal, reload }) => { const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); const [agent, setAgent] = useState(''); const [extension, setExtension] = useState(existingExtension || ''); const query = useMemo(() => ({ type: 'available' as const, userId: agent }), [agent]); @@ -24,9 +26,9 @@ const AssignAgentModal: FC = ({ existingExtension, close const handleAssignment = useMutableCallback(async () => { try { - await assignAgent({ userId: agent, extension }); + await assignAgent({ username: agent, extension }); } catch (error) { - console.log(error); + dispatchToastMessage({ type: 'error', message: error.message }); } reload(); closeModal(); @@ -45,7 +47,7 @@ const AssignAgentModal: FC = ({ existingExtension, close {t('Agent_Without_Extensions')} - + diff --git a/client/views/login/AppleOauth/AppleOauthButton.tsx b/client/views/login/AppleOauth/AppleOauthButton.tsx index 672a64e5857a..8c6800da891d 100644 --- a/client/views/login/AppleOauth/AppleOauthButton.tsx +++ b/client/views/login/AppleOauth/AppleOauthButton.tsx @@ -73,6 +73,10 @@ export const AppleOauthButton: FC = () => { }, [scriptLoadedHandler]); useLayoutEffect(() => { + if (!enabled) { + return; + } + const script = document.createElement('script'); script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js'; script.async = true; @@ -81,7 +85,7 @@ export const AppleOauthButton: FC = () => { return (): void => { document.body.removeChild(script); }; - }, []); + }, [enabled]); if (!enabled) { return null; diff --git a/client/views/teams/CreateTeamModal/CreateTeamModal.tsx b/client/views/teams/CreateTeamModal/CreateTeamModal.tsx index b231b7ec7cc5..8da626fe91cd 100644 --- a/client/views/teams/CreateTeamModal/CreateTeamModal.tsx +++ b/client/views/teams/CreateTeamModal/CreateTeamModal.tsx @@ -303,7 +303,7 @@ const CreateTeamModal: FC = ({ onClose }) => { ({t('optional')}) - + diff --git a/definition/rest/v1/voip.ts b/definition/rest/v1/voip.ts index 89994a27ed93..4cf42fbf630f 100644 --- a/definition/rest/v1/voip.ts +++ b/definition/rest/v1/voip.ts @@ -37,6 +37,9 @@ export type VoipEndpoints = { POST: (params: { userId: string; extension: string } | { username: string; extension: string }) => void; DELETE: (params: { username: string }) => void; }; + 'omnichannel/agents/available': { + GET: (params: PaginatedRequest<{ text?: string; includeExtension?: string }>) => PaginatedResult<{ agents: ILivechatAgent[] }>; + }; 'voip/events': { POST: (params: { event: VoipClientEvents; rid: string; comment?: string }) => void; }; diff --git a/package-lock.json b/package-lock.json index e1edfcb89066..eadf333f1d12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.5.0", + "version": "4.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5377,29 +5377,29 @@ } }, "@rocket.chat/css-in-js": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.31.4.tgz", - "integrity": "sha512-WH5faDA62GFBg3v/k1NFX8e9j1km5BNrBxiLXYCIDyf9Um11Mxlo+ssDuww67HNleOcWuRaVtCZGSMXA4r351A==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.31.5.tgz", + "integrity": "sha512-qtSJghYOrLonIdWbFl7hP9E/bUBQKpbQ2KnsP91MsbjCUwFbPdAFnl690yaxHViv1SXgxZsRRSLHKwjcqwBODw==", "requires": { "@emotion/hash": "^0.8.0", - "@rocket.chat/css-supports": "^0.31.4", - "@rocket.chat/memo": "^0.31.4", - "@rocket.chat/stylis-logical-props-middleware": "^0.31.4", + "@rocket.chat/css-supports": "^0.31.5", + "@rocket.chat/memo": "^0.31.5", + "@rocket.chat/stylis-logical-props-middleware": "^0.31.5", "stylis": "~4.0.13" } }, "@rocket.chat/css-supports": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.31.4.tgz", - "integrity": "sha512-JmZtbydVlNhI+cp2LJjJMaaBJDpfOeQ/0TfnTkpCudvUYS5m5Y4v2PPPCn9VR5raBvagqfsd5tXfE5s/ksqDtg==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.31.5.tgz", + "integrity": "sha512-tGohngRSy/X3w/oSORAby5NQMUdE/QyxoFC5SiVtdnfOZhoVgyJIAINlYpg33aSP2SJkkr60pRm2FQx5WYtPjQ==", "requires": { - "@rocket.chat/memo": "^0.31.4" + "@rocket.chat/memo": "^0.31.5" } }, "@rocket.chat/emitter": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/emitter/-/emitter-0.31.4.tgz", - "integrity": "sha512-gRuYcccStL5wOpWLoV3iCq2Lo5n9W14lJd7Qe1obNsezjYIiMHzJaQIWcbzZNDi0v3HiXxaN+xI6i00zuPnOoA==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/emitter/-/emitter-0.31.5.tgz", + "integrity": "sha512-KioMqqL53CksV5aPdxJyRl8QPxiU8GcO7pSicY7y0uU8gZklbnw/Z8J0hcpQkrDh7kkh24Lk8v8NIA56ub15Xw==" }, "@rocket.chat/eslint-config": { "version": "0.4.0", @@ -5411,30 +5411,30 @@ } }, "@rocket.chat/fuselage": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.31.4.tgz", - "integrity": "sha512-R9v6/cMGKhWJnl763A41ifCf1aQSi86m+U7irMrCRT39Ha2QAt3cHB8puFZsn2CqOYHd+Ygw9M4nObm+8Oz+og==", - "requires": { - "@rocket.chat/css-in-js": "^0.31.4", - "@rocket.chat/css-supports": "^0.31.4", - "@rocket.chat/fuselage-tokens": "^0.31.4", - "@rocket.chat/memo": "^0.31.4", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.31.5.tgz", + "integrity": "sha512-H3FtudLb42OvZo9QyW1SqlI3a9KygzdGtv5KKqlcgPpTn1fzcKcEh01WYd9Kz9FBVXYq3qhjGnnEkSUFB8DnUw==", + "requires": { + "@rocket.chat/css-in-js": "^0.31.5", + "@rocket.chat/css-supports": "^0.31.5", + "@rocket.chat/fuselage-tokens": "^0.31.5", + "@rocket.chat/memo": "^0.31.5", "invariant": "^2.2.4", "react-keyed-flatten-children": "^1.3.0" } }, "@rocket.chat/fuselage-hooks": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.31.4.tgz", - "integrity": "sha512-+6dJFU7MsojUffpifBCw0cvAt3IUp/dMUJa2ANBLsoBxLtCxYGAW8fFI7whilgnIzSXTiRrGgY1ePtJa3zS3ig==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.31.5.tgz", + "integrity": "sha512-GsdEgAQycbzWo/eey0iodPMKuWClYjgBxz7BXISAXbFU8PzB4/HgVGGo/ER0SXO9pb/hDZf3pk9weW79tCai8Q==", "requires": { "@testing-library/user-event": "^13.5.0" } }, "@rocket.chat/fuselage-polyfills": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.31.4.tgz", - "integrity": "sha512-ZEtKQwMJyX+roUmsnozSUgWXPXgpngw5LNHHNchR6tgIKaqVyMgXdNoFeHcBlpucTpuw5UQsz3Q2KgYyO4d4Lw==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.31.5.tgz", + "integrity": "sha512-7dkyKHSLHKEteQrhbzr1tRfoAocG6eDfSDmaKCxm6wrwxeDvO7HDHQ2OJZOOB+Y/zbCz44k+9QOBFRXILCgDxw==", "requires": { "@juggle/resize-observer": "^3.3.1", "clipboard-polyfill": "^3.0.3", @@ -5445,19 +5445,19 @@ } }, "@rocket.chat/fuselage-tokens": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.31.4.tgz", - "integrity": "sha512-tKU2DrpHylLyvT3QOb8WvPSUMvjP460miwtkjHST6Jl5Kza6iK2TbkkrjrXIoGX1GocFoIM/gIFOwA2EUJs0ew==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.31.5.tgz", + "integrity": "sha512-6vYPDM2ntjz/LR/PgIW1GtEDLtDJrUXFy6QEBhfVCUjO+1zSLZ2wVv5EtRawm5QYbsZN9ZC58tBEi/ZEBvPkjA==" }, "@rocket.chat/fuselage-ui-kit": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.31.4.tgz", - "integrity": "sha512-x530EyZyRsPJ+4/0b2cP/auHneXcIWyWvUAshBeyS5c1KXbFvEgSCbPfioxmMhKCEh75GlnAiy7Chmp03hBuhQ==", - "requires": { - "@rocket.chat/fuselage": "^0.31.4", - "@rocket.chat/fuselage-hooks": "^0.31.4", - "@rocket.chat/styled": "^0.31.4", - "@rocket.chat/ui-kit": "^0.31.4", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.31.5.tgz", + "integrity": "sha512-JJx/4VlHbnPTflMLCORmFrg3haWO8cPdYSIbcVrkrESsRzGXIrPmg0XHcGS2WYdU+8BxceUyvobEXSQWjj9N4w==", + "requires": { + "@rocket.chat/fuselage": "^0.31.5", + "@rocket.chat/fuselage-hooks": "^0.31.5", + "@rocket.chat/styled": "^0.31.5", + "@rocket.chat/ui-kit": "^0.31.5", "tslib": "^2.3.1" }, "dependencies": { @@ -5469,14 +5469,14 @@ } }, "@rocket.chat/icons": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.31.4.tgz", - "integrity": "sha512-1QvpZLd92/lyDHN7pXPtcZ0+oEEpXzyjTek1WJBDzhHFFdF14XKT2gtk/UMOG73F1HscTrYDKYnWv4jNsPN09w==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.31.5.tgz", + "integrity": "sha512-p67kcmnWDfV3ejRq890DmGFXWX77DfhKvBq/vC/GN5RnXAxubH7zsUqLSvPotCcvwO5pllm1CjhoVAmDrHc1FA==" }, "@rocket.chat/livechat": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.12.0.tgz", - "integrity": "sha512-ePx2xLI3VRI23H+jgjsWfuO5o0x+pyPRPZWTE9pjH1MBxwLG5pteq8NVBX80WSQL+Lawu6k154B7lGhSsO5tQA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.12.1.tgz", + "integrity": "sha512-Zn3WSthJtljibTome78jjOMpS2vCckCzqpFsJxQ/BBuXuXV3c04ow7YZDOqlxS8QK0Zy9/5Px+PhWW+MJ+5oXQ==", "dev": true, "requires": { "@kossnocorp/desvg": "^0.2.0", @@ -5542,12 +5542,12 @@ } }, "@rocket.chat/logo": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/logo/-/logo-0.31.4.tgz", - "integrity": "sha512-uJd/LP6pIOPF12L19VAGbYmoIcKi81S2HRRkjVsMDyzV91ZWOI2QNRnwqdnUNDCCSEH2HcCrXylT3MttXebDFw==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/logo/-/logo-0.31.5.tgz", + "integrity": "sha512-Jdl5fmbpBZDuqgzADfbh7ZW6FqBxOc6yQkddqBviycrtRnYDSahPSVl72O4Cck26AXb623LVe7fY7oTK6+evEg==", "requires": { - "@rocket.chat/fuselage-hooks": "^0.31.4", - "@rocket.chat/styled": "^0.31.4", + "@rocket.chat/fuselage-hooks": "^0.31.5", + "@rocket.chat/styled": "^0.31.5", "tslib": "^2.3.1" }, "dependencies": { @@ -5559,14 +5559,14 @@ } }, "@rocket.chat/memo": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.31.4.tgz", - "integrity": "sha512-Z67x7ymYumWA4WQV6R93OilLGD3a+esJKtx0pNNY2rwKH4VY3ecAleSef9XnZ2LDs2ehwHufFDJqTySVQhqe8g==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.31.5.tgz", + "integrity": "sha512-wzRGQO3qZ4NvMtiC3kJsK5HDIovtZu8D4r1juK47jirxEZXB3/8uxbOk3ICQIi2tGulyVTnojV/dfolBJeY+FA==" }, "@rocket.chat/message-parser": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/message-parser/-/message-parser-0.31.4.tgz", - "integrity": "sha512-6ljh/zz/lH4bM9M9f2Zk0r0j/c1VjG2Pc6L9yozVKZ2B0rSKCnG5XuL/gN9rPMmfX75dj9qdeiW+qKwSSpsh7A==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/message-parser/-/message-parser-0.31.5.tgz", + "integrity": "sha512-TVckPikRlPIrWqxF1OvL+YIf2Q1zgtIjDL5isWjSG4x3AVkguuT00Nm7xIAEBc/9wStyx5xHGgQaF3o0Q4AnsQ==" }, "@rocket.chat/mp3-encoder": { "version": "0.24.0", @@ -5577,15 +5577,15 @@ } }, "@rocket.chat/onboarding-ui": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/onboarding-ui/-/onboarding-ui-0.31.4.tgz", - "integrity": "sha512-feruACZNuA6zw9ScJtTsARbvTEcsWTGSr7B4bvE15eIUxUqk4+w14KDlS3kx1LwmDknSnrylPuT7LEwMZnDHfg==", - "requires": { - "@rocket.chat/fuselage": "^0.31.4", - "@rocket.chat/fuselage-hooks": "^0.31.4", - "@rocket.chat/icons": "^0.31.4", - "@rocket.chat/logo": "^0.31.4", - "@rocket.chat/styled": "^0.31.4", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/onboarding-ui/-/onboarding-ui-0.31.5.tgz", + "integrity": "sha512-xbQzbq0TNijUbQC4ffR2nTrL60gt6/lsE5yzhLXfJmT5mPOf1kyM+fu+4Ork7VkD6jo3JB3cPH64dA3bIfOUUw==", + "requires": { + "@rocket.chat/fuselage": "^0.31.5", + "@rocket.chat/fuselage-hooks": "^0.31.5", + "@rocket.chat/icons": "^0.31.5", + "@rocket.chat/logo": "^0.31.5", + "@rocket.chat/styled": "^0.31.5", "i18next": "~21.6.11", "react-hook-form": "~7.27.0", "react-i18next": "~11.15.4", @@ -5593,9 +5593,9 @@ }, "dependencies": { "i18next": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.11.tgz", - "integrity": "sha512-tJ2+o0lVO+fhi8bPkCpBAeY1SgkqmQm5NzgPWCQssBrywJw98/o+Kombhty5nxQOpHtvMmsxcOopczUiH6bJxQ==", + "version": "21.6.12", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.12.tgz", + "integrity": "sha512-xlGTPdu2g5PZEUIE6TA1mQ9EIAAv9nMFONzgwAIrKL/KTmYYWufQNGgOmp5Og1PvgUji+6i1whz0rMdsz1qaKw==", "requires": { "@babel/runtime": "^7.12.0" } @@ -5655,9 +5655,9 @@ } }, "@rocket.chat/string-helpers": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/string-helpers/-/string-helpers-0.31.4.tgz", - "integrity": "sha512-tDphtvQ+kE1PJBYhynZrFJ2dDOwDkzcCgt1lToJ+C6d13uWcngWNhl0TpM71L65Q0FcBVP0hJE7jaD2gPDbwjQ==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/string-helpers/-/string-helpers-0.31.5.tgz", + "integrity": "sha512-MIQWeF69YF7/pazI59zZr1ZT04oHlg3oTyks2xCgh2IL3ZHAMhJcJ0yzl0GpigUbxhQ3OVI39fE7/5VanV6nhQ==", "requires": { "tslib": "^2.3.1" }, @@ -5670,11 +5670,11 @@ } }, "@rocket.chat/styled": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/styled/-/styled-0.31.4.tgz", - "integrity": "sha512-tnuwRllCUAnjCXmIyyrMTlL3OvRmCNrL7qlixdrIOzSpDiF7K+pl6Iq2u1jfO3clUcQOVk39BjdH98E/Lx99WA==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/styled/-/styled-0.31.5.tgz", + "integrity": "sha512-ai3ixlmReEEyn/VsJuoEiAXdreHlpH8sYspqeBzfR3GPrIuNGePh5a7GLgdCYh2wrsdSdB0eSBzhf0UwJsT8Vg==", "requires": { - "@rocket.chat/css-in-js": "^0.31.4", + "@rocket.chat/css-in-js": "^0.31.5", "tslib": "^2.3.1" }, "dependencies": { @@ -5686,11 +5686,11 @@ } }, "@rocket.chat/stylis-logical-props-middleware": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.31.4.tgz", - "integrity": "sha512-rjBqhfIueoHVez7CVz6AyEfW2t/vt3fW5+jSWvGGYR7ao1JHRpd1yLPYdsqM+gCr4pcQ6NhCW/cZsIO7cbaw7g==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.31.5.tgz", + "integrity": "sha512-GhpURYo9IY9fS5UYy+tJqfcqCPeTfXVIhSJUlyIf8b8RDSUTH5u4n0Gkvm0kXMMY6vo8UGsT+HrP6CZmbstbbg==", "requires": { - "@rocket.chat/css-supports": "^0.31.4", + "@rocket.chat/css-supports": "^0.31.5", "tslib": "^2.3.1" }, "dependencies": { @@ -5702,9 +5702,9 @@ } }, "@rocket.chat/ui-kit": { - "version": "0.31.4", - "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.31.4.tgz", - "integrity": "sha512-wMoFyEbPh4bf5hZCY+vJ7qzGs5vL8zdZ8y2t5iyJ9WENcxqh/uylYAvfsEJhMx9D2GFuhg9gZreYGDTlxk1uFQ==" + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.31.5.tgz", + "integrity": "sha512-jmwFo//3th/VPu10qoMbL5CE8l6reaxrbo1JFoxGh+sfpBzeYmpmQ16hoLfydfRky9O+S4xBcvvZt+CIIKZ/zg==" }, "@samverschueren/stream-to-observable": { "version": "0.3.1", @@ -22109,9 +22109,9 @@ "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==" }, "history": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz", - "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", "dev": true, "requires": { "@babel/runtime": "^7.7.6" @@ -30352,9 +30352,9 @@ "integrity": "sha1-3F4yN2WYXd/cv9r8MUGpVprvdak=" }, "preact": { - "version": "10.6.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.4.tgz", - "integrity": "sha512-WyosM7pxGcndU8hY0OQlLd54tOU+qmG45QXj2dAYrL11HoyU/EzOSTlpJsirbBr1QW7lICxSsVJJmcmUglovHQ==", + "version": "10.6.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz", + "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==", "dev": true }, "preact-i18nline": { @@ -31712,9 +31712,9 @@ "integrity": "sha512-Q2zaeQFXdVQ8l3hcywhltH+Nzj4vo50wMVujHDVN/1Xy9IOaSZJwYBXA2CYTpK6rq41fnXviw3jTLb04c7Gu9Q==" }, "react-i18next": { - "version": "11.15.4", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.4.tgz", - "integrity": "sha512-jKJNAcVcbPGK+yrTcXhLblgPY16n6NbpZZL3Mk8nswj1v3ayIiUBVDU09SgqnT+DluyQBS97hwSvPU5yVFG0yg==", + "version": "11.15.5", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.5.tgz", + "integrity": "sha512-vBWuVEQgrhZrGKpyv8FmJ7Zs5jRQWl794Tte7yzJ0okZqqi3jd6j2pLYNg441WcREsbIOvWdiDXbY7W6E93p1A==", "requires": { "@babel/runtime": "^7.14.5", "html-escaper": "^2.0.2", diff --git a/package.json b/package.json index f5b05681b286..c762fe2b2737 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.5.0", + "version": "4.5.1", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" @@ -63,7 +63,7 @@ "@babel/preset-react": "^7.14.5", "@babel/register": "^7.14.5", "@rocket.chat/eslint-config": "^0.4.0", - "@rocket.chat/livechat": "^1.12.0", + "@rocket.chat/livechat": "^1.12.1", "@settlin/spacebars-loader": "^1.0.9", "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-postcss": "^2.0.0", @@ -178,21 +178,21 @@ "@nivo/line": "0.62.0", "@nivo/pie": "0.73.0", "@rocket.chat/apps-engine": "^1.31.0", - "@rocket.chat/css-in-js": "~0.31.4", - "@rocket.chat/emitter": "~0.31.4", - "@rocket.chat/fuselage": "~0.31.4", - "@rocket.chat/fuselage-hooks": "~0.31.4", - "@rocket.chat/fuselage-polyfills": "~0.31.4", - "@rocket.chat/fuselage-tokens": "~0.31.4", - "@rocket.chat/fuselage-ui-kit": "~0.31.4", - "@rocket.chat/icons": "~0.31.4", - "@rocket.chat/logo": "~0.31.4", - "@rocket.chat/memo": "~0.31.4", - "@rocket.chat/message-parser": "~0.31.4", + "@rocket.chat/css-in-js": "^0.31.5", + "@rocket.chat/emitter": "^0.31.5", + "@rocket.chat/fuselage": "^0.31.5", + "@rocket.chat/fuselage-hooks": "^0.31.5", + "@rocket.chat/fuselage-polyfills": "^0.31.5", + "@rocket.chat/fuselage-tokens": "^0.31.5", + "@rocket.chat/fuselage-ui-kit": "^0.31.5", + "@rocket.chat/icons": "^0.31.5", + "@rocket.chat/logo": "^0.31.5", + "@rocket.chat/memo": "^0.31.5", + "@rocket.chat/message-parser": "^0.31.5", "@rocket.chat/mp3-encoder": "^0.24.0", - "@rocket.chat/onboarding-ui": "~0.31.4", - "@rocket.chat/string-helpers": "~0.31.4", - "@rocket.chat/ui-kit": "~0.31.4", + "@rocket.chat/onboarding-ui": "^0.31.5", + "@rocket.chat/string-helpers": "^0.31.5", + "@rocket.chat/ui-kit": "^0.31.5", "@slack/client": "^4.12.0", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.177", diff --git a/packages/rocketchat-i18n/i18n/ar.i18n.json b/packages/rocketchat-i18n/i18n/ar.i18n.json index b588b77f8e4c..6dcc07d1bccd 100644 --- a/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -2890,7 +2890,11 @@ "Why_do_you_want_to_report_question_mark": "لماذا تريد الإبلاغ عن؟", "will_be_able_to": "سوف يكون قادر على", "Worldwide": "في جميع أنحاء العالم", - "Would_you_like_to_return_the_inquiry": "هل ترغب بإعادة الطلب؟", + "Would_you_like_to_return_the_inquiry": "هل ترغب في إعادة الاستفسار؟", + "Would_you_like_to_return_the_queue": "هل ترغب في إعادة هذه الغرفة إلى قائمة الانتظار؟ سيتم الاحتفاظ بكل محفوظات المحادثات في الغرفة.", + "Would_you_like_to_place_chat_on_hold": "هل ترغب في وضع هذه الدردشة قيد الانتظار؟", + "Wrap_up_the_call": "إتمام المكالمة", + "Wrap_Up_Notes": "ملحوظات مختصرة", "Yes": "نعم", "Yes_archive_it": "نعم، قم بأرشفته!", "Yes_clear_all": "نعم، حذف الكل!", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 4b2322574bfe..021a8daabea8 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -4110,7 +4110,10 @@ "will_be_able_to": "wird in der Lage sein,", "Worldwide": "Weltweit", "Would_you_like_to_return_the_inquiry": "Anfrage zurückgeben?", - "Would_you_like_to_place_chat_on_hold": "Möchtest du diesen Chat in die Warteschleife legen?", + "Would_you_like_to_return_the_queue": "Möchten Sie diesen Raum zurück in die Warteschlange stellen? Der gesamte Gesprächsverlauf für den Raum wird aufbewahrt.", + "Would_you_like_to_place_chat_on_hold": "Möchten Sie diesen Chat in die Warteschleife stellen?", + "Wrap_up_the_call": "Gespräch abschließen", + "Wrap_Up_Notes": "Abschließende Notizen", "Yes": "Ja", "Yes_archive_it": "Ja, archivieren!", "Yes_clear_all": "Ja, alles löschen!", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 09d371da645d..3f625aa1b06f 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1490,6 +1490,7 @@ "Do_not_display_unread_counter": "Do not display any counter of this channel", "Do_not_provide_this_code_to_anyone": "Do not provide this code to anyone.", "Do_Nothing": "Do Nothing", + "Do_you_have_any_notes_for_this_conversation": "Do you have any notes for this conversation?", "Do_you_want_to_accept": "Do you want to accept?", "Do_you_want_to_change_to_s_question": "Do you want to change to %s?", "Document_Domain": "Document Domain", @@ -4799,8 +4800,8 @@ "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", "Would_you_like_to_return_the_queue": "Would you like to move back this room to the queue? All conversation history will be kept on the room.", "Would_you_like_to_place_chat_on_hold": "Would you like to place this chat On-Hold?", - "Wrap_Up_the_Call": "Wrap Up the Call", - "Wrap_Up_Notes": "Wrap Up Notes", + "Wrap_up_the_call": "Wrap-up the call", + "Wrap_Up_Notes": "Wrap-Up Notes", "Yes": "Yes", "Yes_archive_it": "Yes, archive it!", "Yes_clear_all": "Yes, clear all!", diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index 40b8e85e026b..5a41e0db832e 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -4728,6 +4728,8 @@ "Would_you_like_to_return_the_inquiry": "Souhaitez-vous retourner la demande ?", "Would_you_like_to_return_the_queue": "Souhaitez-vous replacer ce salon dans la file d'attente ? Tout l'historique des conversations sera conservé dans le salon.", "Would_you_like_to_place_chat_on_hold": "Souhaitez-vous mettre ce chat en attente ?", + "Wrap_up_the_call": "Terminer l'appel", + "Wrap_Up_Notes": "Notes de conclusion", "Yes": "Oui", "Yes_archive_it": "Oui, archivez-le !", "Yes_clear_all": "Oui, effacez tout !", diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index 674df759ead3..c146daa19e29 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -4112,7 +4112,11 @@ "Will_be_available_here_after_saving": "保存後、こちらからご利用いただけます。", "Without_priority": "優先権なし", "Worldwide": "全世界", - "Would_you_like_to_return_the_inquiry": "お問い合わせを返信しますか?", + "Would_you_like_to_return_the_inquiry": "問い合わせを返しますか?", + "Would_you_like_to_return_the_queue": "このルームをキューに戻しますか?すべての会話履歴がルームに保持されます。", + "Would_you_like_to_place_chat_on_hold": "このチャットを保留中にしますか?", + "Wrap_up_the_call": "通話の要約", + "Wrap_Up_Notes": "メモの要約", "Yes": "はい", "Yes_archive_it": "はい、アーカイブしてください!", "Yes_clear_all": "はい、すべてクリアします!", diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index e090982dc78a..f6ed85b4ffdc 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -4728,6 +4728,8 @@ "Would_you_like_to_return_the_inquiry": "Wilt u de aanvraag retourneren?", "Would_you_like_to_return_the_queue": "Wilt u deze kamer weer in de wachtrij plaatsen? Alle gespreksgeschiedenis wordt in de kamer bewaard.", "Would_you_like_to_place_chat_on_hold": "Wilt u deze chat on-hold zetten?", + "Wrap_up_the_call": "Rond het gesprek af", + "Wrap_Up_Notes": "Afsluitende notities", "Yes": "Ja", "Yes_archive_it": "Ja, archiveer het!", "Yes_clear_all": "Ja, alles wissen!", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 7026d1dfdfe0..733876540a32 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -4706,6 +4706,8 @@ "Would_you_like_to_return_the_inquiry": "Gostaria de devolver o inquérito?", "Would_you_like_to_return_the_queue": "Você gostaria de mover este chat de volta para a fila? Todo o histórico da conversa será mantido.", "Would_you_like_to_place_chat_on_hold": "Gostaria de colocar essa conversa Em Espera?", + "Wrap_up_the_call": "Encerrar a chamada", + "Wrap_Up_Notes": "Notas de encerramento", "Yes": "Sim", "Yes_archive_it": "Sim, arquivar!", "Yes_clear_all": "Sim, limpar tudo!", diff --git a/server/lib/logger/getPino.ts b/server/lib/logger/getPino.ts index 5c526b82abdf..30b3219e8182 100644 --- a/server/lib/logger/getPino.ts +++ b/server/lib/logger/getPino.ts @@ -21,7 +21,6 @@ const mainPino = pino({ subscription: 35, startup: 51, }, - name: '', level: 'warn', timestamp: pino.stdTimeFunctions.isoTime, ...(process.env.NODE_ENV !== 'production' diff --git a/server/sdk/types/IOmnichannelVoipService.ts b/server/sdk/types/IOmnichannelVoipService.ts index e41179e1b63b..ef4512223338 100644 --- a/server/sdk/types/IOmnichannelVoipService.ts +++ b/server/sdk/types/IOmnichannelVoipService.ts @@ -30,4 +30,11 @@ export interface IOmnichannelVoipService { ): Promise; getExtensionListWithAgentData(): Promise; findVoipRooms(filter: FindVoipRoomsParams): Promise>; + getAvailableAgents( + includeExtension?: string, + text?: string, + count?: number, + offset?: number, + sort?: Record, + ): Promise<{ agents: ILivechatAgent[]; total: number }>; } diff --git a/server/services/omnichannel-voip/service.ts b/server/services/omnichannel-voip/service.ts index d298b3b2d325..3dfec93c94f5 100644 --- a/server/services/omnichannel-voip/service.ts +++ b/server/services/omnichannel-voip/service.ts @@ -458,4 +458,21 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn this.logger.warn({ msg: 'Invalid room type or event type', type: room.t, event }); } } + + async getAvailableAgents( + includeExtension?: string, + text?: string, + count?: number, + offset?: number, + sort?: Record, + ): Promise<{ agents: ILivechatAgent[]; total: number }> { + const cursor = this.users.getAvailableAgentsIncludingExt(includeExtension, text, { count, skip: offset, sort }); + const agents = await cursor.toArray(); + const total = await cursor.count(); + + return { + agents, + total, + }; + } } diff --git a/server/services/team/service.ts b/server/services/team/service.ts index ef963152d987..5ed035d525fa 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -374,7 +374,9 @@ export class TeamService extends ServiceClassInternal implements ITeamService { throw new Error('error-no-owner-channel'); } - Messages.createUserAddRoomToTeamWithRoomIdAndUser(team.roomId, room.name, user); + if (room.t === 'c') { + Messages.createUserAddRoomToTeamWithRoomIdAndUser(team.roomId, room.name, user); + } room.teamId = teamId; } @@ -420,7 +422,9 @@ export class TeamService extends ServiceClassInternal implements ITeamService { delete room.teamDefault; this.RoomsModel.unsetTeamById(room._id); - Messages.createUserRemoveRoomFromTeamWithRoomIdAndUser(team.roomId, room.name, user); + if (room.t === 'c') { + Messages.createUserRemoveRoomFromTeamWithRoomIdAndUser(team.roomId, room.name, user); + } return { ...room,