diff --git a/.changeset/late-papayas-swim.md b/.changeset/late-papayas-swim.md new file mode 100644 index 0000000000000..7431abc3a5cb7 --- /dev/null +++ b/.changeset/late-papayas-swim.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Adds deprecation warning on `removeCannedResponse`; diff --git a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEdit.tsx b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEdit.tsx index 550904a294eb8..2841f73337599 100644 --- a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEdit.tsx +++ b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEdit.tsx @@ -6,7 +6,6 @@ import { useId, memo, useCallback } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import CannedResponseForm from './components/CannedResponseForm'; -import { useRemoveCannedResponse } from './useRemoveCannedResponse'; import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../components/Page'; export type CannedResponseEditFormData = { @@ -21,6 +20,7 @@ export type CannedResponseEditFormData = { type CannedResponseEditProps = { cannedResponseData?: Serialized; departmentData?: Serialized; + onDelete?: () => void; }; const getInitialData = (cannedResponseData: Serialized | undefined) => ({ @@ -32,7 +32,7 @@ const getInitialData = (cannedResponseData: Serialized { +const CannedResponseEdit = ({ cannedResponseData, onDelete }: CannedResponseEditProps) => { const t = useTranslation(); const router = useRouter(); const dispatchToastMessage = useToastMessageDispatch(); @@ -48,8 +48,6 @@ const CannedResponseEdit = ({ cannedResponseData }: CannedResponseEditProps) => formState: { isDirty }, } = methods; - const handleDelete = useRemoveCannedResponse(); - const handleSave = useCallback( async ({ departmentId, ...data }: CannedResponseEditFormData) => { try { @@ -82,7 +80,7 @@ const CannedResponseEdit = ({ cannedResponseData }: CannedResponseEditProps) => > {cannedResponseData?._id && ( - diff --git a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx index e6e457241e82c..5d2db302d483b 100644 --- a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx +++ b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx @@ -6,12 +6,15 @@ import { useTranslation } from 'react-i18next'; import CannedResponseEdit from './CannedResponseEdit'; import CannedResponseEditWithDepartmentData from './CannedResponseEditWithDepartmentData'; +import { useRemoveCannedResponse } from './useRemoveCannedResponse'; import { FormSkeleton } from '../../components/Skeleton'; const CannedResponseEditWithData = ({ cannedResponseId }: { cannedResponseId: IOmnichannelCannedResponse['_id'] }) => { const { t } = useTranslation(); const getCannedResponseById = useEndpoint('GET', '/v1/canned-responses/:_id', { _id: cannedResponseId }); + const handleDelete = useRemoveCannedResponse(cannedResponseId); + const { data, isPending, isError } = useQuery({ queryKey: ['getCannedResponseById', cannedResponseId], queryFn: async () => getCannedResponseById(), @@ -30,10 +33,10 @@ const CannedResponseEditWithData = ({ cannedResponseId }: { cannedResponseId: IO } if (data?.cannedResponse?.scope === 'department') { - return ; + return ; } - return ; + return ; }; export default CannedResponseEditWithData; diff --git a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx index 24a14b09ac1dc..1a2a9963dea2f 100644 --- a/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx +++ b/apps/meteor/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx @@ -9,7 +9,13 @@ import CannedResponseEdit from './CannedResponseEdit'; import { FormSkeleton } from '../../components/Skeleton'; import { omnichannelQueryKeys } from '../../lib/queryKeys'; -const CannedResponseEditWithDepartmentData = ({ cannedResponseData }: { cannedResponseData: Serialized }) => { +const CannedResponseEditWithDepartmentData = ({ + cannedResponseData, + onDelete, +}: { + cannedResponseData: Serialized; + onDelete: () => void; +}) => { const departmentId = useMemo(() => cannedResponseData?.departmentId, [cannedResponseData]) as string; const getDepartment = useEndpoint('GET', '/v1/livechat/department/:_id', { _id: departmentId }); @@ -39,7 +45,7 @@ const CannedResponseEditWithDepartmentData = ({ cannedResponseData }: { cannedRe ); } - return ; + return ; }; export default CannedResponseEditWithDepartmentData; diff --git a/apps/meteor/client/omnichannel/cannedResponses/CannedResponsesTable.tsx b/apps/meteor/client/omnichannel/cannedResponses/CannedResponsesTable.tsx index ea344a6a13e15..45c4ebabf0703 100644 --- a/apps/meteor/client/omnichannel/cannedResponses/CannedResponsesTable.tsx +++ b/apps/meteor/client/omnichannel/cannedResponses/CannedResponsesTable.tsx @@ -1,4 +1,4 @@ -import { Box, IconButton, Pagination } from '@rocket.chat/fuselage'; +import { Box, Pagination } from '@rocket.chat/fuselage'; import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { UserAvatar } from '@rocket.chat/ui-avatar'; import { useTranslation, usePermission, useToastMessageDispatch, useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; @@ -6,7 +6,7 @@ import { hashKey, useQuery } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import CannedResponseFilter from './CannedResponseFilter'; -import { useRemoveCannedResponse } from './useRemoveCannedResponse'; +import RemoveCannedResponseButton from './RemoveCannedResponseButton'; import GenericNoResults from '../../components/GenericNoResults'; import { GenericTable, @@ -75,8 +75,6 @@ const CannedResponsesTable = () => { router.navigate(`/omnichannel/canned-responses/edit/${id}`); }); - const handleDelete = useRemoveCannedResponse(); - const defaultOptions = useMemo( () => ({ global: t('Public'), @@ -170,19 +168,7 @@ const CannedResponsesTable = () => { {getTime(_createdAt)} {tags.join(', ')} - {!(scope === 'global' && isMonitor && !isManager) && ( - - { - e.stopPropagation(); - handleDelete(_id); - }} - /> - - )} + {!(scope === 'global' && isMonitor && !isManager) && } ))} diff --git a/apps/meteor/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx b/apps/meteor/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx new file mode 100644 index 0000000000000..ddc0d3c1e8d97 --- /dev/null +++ b/apps/meteor/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx @@ -0,0 +1,28 @@ +import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; +import { IconButton } from '@rocket.chat/fuselage'; +import { useTranslation } from 'react-i18next'; + +import { useRemoveCannedResponse } from './useRemoveCannedResponse'; +import { GenericTableCell } from '../../components/GenericTable'; + +const RemoveCannedResponseButton = ({ id }: { id: IOmnichannelCannedResponse['_id'] }) => { + const { t } = useTranslation(); + + const handleDelete = useRemoveCannedResponse(id); + + return ( + + { + e.stopPropagation(); + handleDelete(); + }} + /> + + ); +}; + +export default RemoveCannedResponseButton; diff --git a/apps/meteor/client/omnichannel/cannedResponses/useRemoveCannedResponse.tsx b/apps/meteor/client/omnichannel/cannedResponses/useRemoveCannedResponse.tsx index e853ac7412bf6..be706985158e3 100644 --- a/apps/meteor/client/omnichannel/cannedResponses/useRemoveCannedResponse.tsx +++ b/apps/meteor/client/omnichannel/cannedResponses/useRemoveCannedResponse.tsx @@ -1,22 +1,23 @@ +import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { GenericModal } from '@rocket.chat/ui-client'; -import { useSetModal, useToastMessageDispatch, useRouter, useMethod } from '@rocket.chat/ui-contexts'; +import { useSetModal, useToastMessageDispatch, useRouter, useEndpoint } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; -export const useRemoveCannedResponse = () => { +export const useRemoveCannedResponse = (id: IOmnichannelCannedResponse['_id']) => { const { t } = useTranslation(); const setModal = useSetModal(); const router = useRouter(); const queryClient = useQueryClient(); const dispatchToastMessage = useToastMessageDispatch(); - const removeCannedResponse = useMethod('removeCannedResponse'); + const removeCannedResponse = useEndpoint('DELETE', '/v1/canned-responses/:_id', { _id: id }); - const handleDelete = useEffectEvent((id: string) => { + const handleDelete = useEffectEvent(() => { const onDeleteCannedResponse: () => Promise = async () => { try { - await removeCannedResponse(id); + await removeCannedResponse(); queryClient.invalidateQueries({ queryKey: ['getCannedResponses'], }); diff --git a/apps/meteor/ee/app/api-enterprise/server/canned-responses.ts b/apps/meteor/ee/app/api-enterprise/server/canned-responses.ts index afe018f9a3ff4..4fa92b5b968a0 100644 --- a/apps/meteor/ee/app/api-enterprise/server/canned-responses.ts +++ b/apps/meteor/ee/app/api-enterprise/server/canned-responses.ts @@ -1,5 +1,5 @@ import type { ILivechatDepartment, IOmnichannelCannedResponse, IUser } from '@rocket.chat/core-typings'; -import { isPOSTCannedResponsesProps, isDELETECannedResponsesProps, isCannedResponsesProps } from '@rocket.chat/rest-typings'; +import { isPOSTCannedResponsesProps, isCannedResponsesProps, isDELETECannedResponsesProps } from '@rocket.chat/rest-typings'; import type { PaginatedResult, PaginatedRequest } from '@rocket.chat/rest-typings'; import { findAllCannedResponses, findAllCannedResponsesFilter, findOneCannedResponse } from './lib/canned-responses'; @@ -38,6 +38,7 @@ declare module '@rocket.chat/rest-typings' { GET: () => { cannedResponse: IOmnichannelCannedResponse; }; + DELETE: () => void; }; } } @@ -54,6 +55,53 @@ API.v1.addRoute( }, ); +/** + * @deprecated + * @openapi + * /api/v1/canned-responses: + * delete: + * deprecated: true + * security: + * $ref: '#/security/authenticated' + * parameters: + * - in: body + * name: body + * description: | + * **_id** (required): Canned Response ID to be removed. + * schema: + * type: object + * required: + * - _id + * properties: + * _id: + * type: string + * tags: + * - Canned_Responses + * responses: + * 200: + * description: Successful Response + * schema: + * type: object + * properties: + * status: + * type: string + * example: success + * data: + * type: object + * description: The response data + * properties: + * success: + * type: boolean + * example: true + * 401: + * $ref: '#/responses/Unauthorized' + * 403: + * $ref: '#/responses/Forbidden' + * 404: + * $ref: '#/responses/NotFound' + * 500: + * $ref: '#/responses/InternalServerError' + */ API.v1.addRoute( 'canned-responses', { @@ -61,6 +109,7 @@ API.v1.addRoute( permissionsRequired: { GET: ['view-canned-responses'], POST: ['save-canned-responses'], DELETE: ['remove-canned-responses'] }, validateParams: { POST: isPOSTCannedResponsesProps, DELETE: isDELETECannedResponsesProps, GET: isCannedResponsesProps }, license: ['canned-responses'], + deprecations: { DELETE: { version: '8.0.0', alternatives: ['/v1/canned-responses/:_id'] } }, }, { async get() { @@ -104,6 +153,7 @@ API.v1.addRoute( ); return API.v1.success(); }, + // deprecated async delete() { const { _id } = this.bodyParams; await removeCannedResponse(this.userId, _id); @@ -114,7 +164,11 @@ API.v1.addRoute( API.v1.addRoute( 'canned-responses/:_id', - { authRequired: true, permissionsRequired: ['view-canned-responses'], license: ['canned-responses'] }, + { + authRequired: true, + permissionsRequired: { GET: ['view-canned-responses'], DELETE: ['remove-canned-responses'] }, + license: ['canned-responses'], + }, { async get() { const { _id } = this.urlParams; @@ -129,5 +183,10 @@ API.v1.addRoute( return API.v1.success({ cannedResponse }); }, + async delete() { + const { _id } = this.urlParams; + await removeCannedResponse(this.userId, _id); + return API.v1.success(); + }, }, ); diff --git a/apps/meteor/ee/app/canned-responses/server/methods/removeCannedResponse.ts b/apps/meteor/ee/app/canned-responses/server/methods/removeCannedResponse.ts index 3d611672c64f0..d1ab09e578fba 100644 --- a/apps/meteor/ee/app/canned-responses/server/methods/removeCannedResponse.ts +++ b/apps/meteor/ee/app/canned-responses/server/methods/removeCannedResponse.ts @@ -4,6 +4,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; +import { methodDeprecationLogger } from '../../../../../app/lib/server/lib/deprecationWarningLogger'; import notifications from '../../../../../app/notifications/server/lib/Notifications'; declare module '@rocket.chat/ddp-client' { @@ -36,6 +37,7 @@ export const removeCannedResponse = async (uid: string, _id: string): Promise({ async removeCannedResponse(_id) { + methodDeprecationLogger.method('removeCannedResponse', '8.0.0', 'DELETE /v1/canned-responses/:_id'); const uid = Meteor.userId(); if (!uid) { diff --git a/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts b/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts index 8099990a6a72e..74c70a50fe7a0 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts @@ -250,7 +250,7 @@ import { IS_EE } from '../../../e2e/config/constants'; describe('[DELETE] canned-responses', () => { it('should fail if user dont have remove-canned-responses permission', async () => { await updatePermission('remove-canned-responses', []); - return request.delete(api('canned-responses')).send({ _id: 'sfdads' }).set(credentials).expect(403); + return request.delete(api('canned-responses/sfdads')).set(credentials).expect(403); }); it('should fail if _id is not on the request', async () => { await updatePermission('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); @@ -259,7 +259,10 @@ import { IS_EE } from '../../../e2e/config/constants'; it('should delete a canned response', async () => { const response = await createCannedResponse(); const { body: cr } = await request.get(api('canned-responses')).set(credentials).query({ shortcut: response.shortcut }).expect(200); - const { body } = await request.delete(api('canned-responses')).send({ _id: cr.cannedResponses[0]._id }).set(credentials).expect(200); + const { body } = await request + .delete(api(`canned-responses/${cr.cannedResponses[0]._id}`)) + .set(credentials) + .expect(200); expect(body).to.have.property('success', true); }); });