diff --git a/.changeset/cuddly-dots-end.md b/.changeset/cuddly-dots-end.md new file mode 100644 index 0000000000000..32eba4e0320de --- /dev/null +++ b/.changeset/cuddly-dots-end.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Adds deprecation warning on `livechat:saveAgentInfo` with new endpoint replacing it; `livechat/agents.saveInfo` diff --git a/apps/meteor/app/livechat/server/api/v1/agent.ts b/apps/meteor/app/livechat/server/api/v1/agent.ts index 0e5c16cb92343..3c271e832b07f 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.ts +++ b/apps/meteor/app/livechat/server/api/v1/agent.ts @@ -1,12 +1,23 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; -import { isGETAgentNextToken, isPOSTLivechatAgentStatusProps } from '@rocket.chat/rest-typings'; +import { + isGETAgentNextToken, + isPOSTLivechatAgentSaveInfoParams, + isPOSTLivechatAgentStatusProps, + POSTLivechatAgentSaveInfoSuccessResponse, + validateBadRequestErrorResponse, + validateForbiddenErrorResponse, + validateUnauthorizedErrorResponse, +} from '@rocket.chat/rest-typings'; import { API } from '../../../../api/server'; +import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; +import { hasRoleAsync } from '../../../../authorization/server/functions/hasRole'; import { RoutingManager } from '../../lib/RoutingManager'; import { getRequiredDepartment } from '../../lib/departmentsLib'; +import { saveAgentInfo } from '../../lib/omni-users'; import { setUserStatusLivechat, allowAgentChangeServiceStatus } from '../../lib/utils'; import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; @@ -124,3 +135,36 @@ API.v1.addRoute( }, }, ); + +const livechatAgentsEndpoints = API.v1.post( + 'livechat/agents.saveInfo', + { + response: { + 200: POSTLivechatAgentSaveInfoSuccessResponse, + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + }, + authRequired: true, + permissionsRequired: ['manage-livechat-agents'], + body: isPOSTLivechatAgentSaveInfoParams, + }, + async function action() { + const { agentId, agentData, agentDepartments } = this.bodyParams; + + if (!(await hasRoleAsync(agentId, 'livechat-agent'))) { + return API.v1.failure('error-user-is-not-agent'); + } + + await saveAgentInfo(agentId, agentData, agentDepartments); + + return API.v1.success(); + }, +); + +type LivechatAgentsEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends LivechatAgentsEndpoints {} +} diff --git a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts index e48f396d5c300..3e761efa352b6 100644 --- a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts +++ b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts @@ -3,6 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; +import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { saveAgentInfo } from '../lib/omni-users'; declare module '@rocket.chat/ddp-client' { @@ -14,6 +15,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'livechat:saveAgentInfo'(_id, agentData, agentDepartments) { + methodDeprecationLogger.method('livechat:saveAgentInfo', '8.0.0', '/v1/livechat/agents.saveInfo'); check(_id, String); check(agentData, Object); check(agentDepartments, [String]); diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx index fcaefef3e5087..4be06d61a5e4b 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx @@ -2,7 +2,7 @@ import type { ILivechatAgent, ILivechatAgentStatus, ILivechatDepartmentAgents } import { Field, FieldLabel, FieldGroup, FieldRow, TextInput, Button, Box, Icon, Select, ButtonGroup } from '@rocket.chat/fuselage'; import type { SelectOption } from '@rocket.chat/fuselage'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useSetting, useMethod, useTranslation, useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useSetting, useTranslation, useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useId, useMemo } from 'react'; import { useForm, Controller, FormProvider } from 'react-hook-form'; @@ -79,17 +79,17 @@ const AgentEdit = ({ agentData, agentDepartments }: AgentEditProps) => { formState: { isDirty }, } = methods; - const saveAgentInfo = useMethod('livechat:saveAgentInfo'); + const saveAgentInfo = useEndpoint('POST', '/v1/livechat/agents.saveInfo'); const saveAgentStatus = useEndpoint('POST', '/v1/livechat/agent.status'); const handleSave = useEffectEvent(async ({ status, departments, ...data }: AgentEditFormData) => { try { await saveAgentStatus({ agentId: agentData._id, status }); - await saveAgentInfo( - agentData._id, - data, - departments.map((dep) => dep.value), - ); + await saveAgentInfo({ + agentId: agentData._id, + agentData: data, + agentDepartments: departments.map((dep) => dep.value), + }); dispatchToastMessage({ type: 'success', message: t('Success') }); router.navigate('/omnichannel/agents'); diff --git a/apps/meteor/tests/data/livechat/users.ts b/apps/meteor/tests/data/livechat/users.ts index c2088ead96e07..0e59471325d33 100644 --- a/apps/meteor/tests/data/livechat/users.ts +++ b/apps/meteor/tests/data/livechat/users.ts @@ -3,7 +3,7 @@ import type { Credentials } from '@rocket.chat/api-client'; import { UserStatus, type ILivechatAgent, type IUser } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; -import { api, credentials, request, methodCall } from '../api-data'; +import { api, credentials, request } from '../api-data'; import { password } from '../user'; import { createUser, login, setUserAway, setUserStatus } from '../users.helper'; import { createAgent, makeAgentAvailable, makeAgentUnavailable } from './rooms'; @@ -104,15 +104,12 @@ export const updateLivechatSettingsForUser = async ( agentDepartments: string[] = [], ): Promise => { await request - .post(methodCall('livechat:saveAgentInfo')) + .post(api('livechat/agents.saveInfo')) .set(credentials) .send({ - message: JSON.stringify({ - method: 'livechat:saveAgentInfo', - params: [agentId, livechatSettings, agentDepartments], - id: 'id', - msg: 'method', - }), + agentId, + agentData: livechatSettings, + agentDepartments, }) .expect('Content-Type', 'application/json') .expect(200); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-agents.spec.ts index 093e5109867db..2c7f203329d11 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-agents.spec.ts @@ -115,8 +115,7 @@ test.describe.serial('OC - Manage Agents', () => { await poOmnichannelAgents.btnEdit.click(); await poOmnichannelAgents.selectDepartment(department.data.name); - const reg = new RegExp(`/api/v1/method.call/${encodeURIComponent('livechat:saveAgentInfo')}`); - const response = page.waitForResponse(reg); + const response = page.waitForResponse('**/api/v1/livechat/agents.saveInfo'); await poOmnichannelAgents.btnSave.click(); /** diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index e5db53a53a785..149d72b754ef1 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -1723,6 +1723,48 @@ const POSTLivechatAgentStatusPropsSchema = { export const isPOSTLivechatAgentStatusProps = ajv.compile(POSTLivechatAgentStatusPropsSchema); +type POSTLivechatAgentSaveInfoParams = { + agentId: string; + agentData: Record; + agentDepartments: string[]; +}; + +const POSTLivechatAgentSaveInfoParamsSchema = { + type: 'object', + properties: { + agentId: { + type: 'string', + }, + agentData: { + type: 'object', + additionalProperties: true, + }, + agentDepartments: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['agentId', 'agentData', 'agentDepartments'], + additionalProperties: false, +}; + +export const isPOSTLivechatAgentSaveInfoParams = ajv.compile(POSTLivechatAgentSaveInfoParamsSchema); + +const POSTLivechatAgentSaveInfoSuccessResponseSchema = { + type: 'object', + properties: { + success: { + type: 'boolean', + enum: [true], + }, + }, + additionalProperties: false, +}; + +export const POSTLivechatAgentSaveInfoSuccessResponse = ajv.compile(POSTLivechatAgentSaveInfoSuccessResponseSchema); + type LivechatAnalyticsAgentsTotalServiceTimeProps = PaginatedRequest<{ start: string; end: string;