Skip to content

Commit 90a1759

Browse files
chore: Adds deprecation warning on livechat:saveCustomField with new endpoint replacing it (RocketChat#36971)
1 parent b74ab6f commit 90a1759

7 files changed

Lines changed: 237 additions & 78 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
"@rocket.chat/rest-typings": patch
4+
---
5+
6+
Adds deprecation warning on `livechat:saveCustomField` with new endpoint replacing it; `livechat/custom-fields.save`

apps/meteor/app/livechat/server/api/v1/customField.ts

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import {
44
isPOSTLivechatCustomFieldParams,
55
isPOSTLivechatCustomFieldsParams,
66
isPOSTLivechatRemoveCustomFields,
7+
isPOSTLivechatSaveCustomFieldsParams,
78
POSTLivechatRemoveCustomFieldSuccess,
9+
POSTLivechatSaveCustomFieldSuccess,
810
validateBadRequestErrorResponse,
11+
validateForbiddenErrorResponse,
912
validateUnauthorizedErrorResponse,
1013
} from '@rocket.chat/rest-typings';
1114

@@ -91,29 +94,73 @@ API.v1.addRoute(
9194
},
9295
);
9396

94-
const livechatCustomFieldsEndpoints = API.v1.post(
95-
'livechat/custom-fields.delete',
96-
{
97-
response: {
98-
200: POSTLivechatRemoveCustomFieldSuccess,
99-
400: validateBadRequestErrorResponse,
100-
401: validateUnauthorizedErrorResponse,
97+
const livechatCustomFieldsEndpoints = API.v1
98+
.post(
99+
'livechat/custom-fields.save',
100+
{
101+
response: {
102+
200: POSTLivechatSaveCustomFieldSuccess,
103+
400: validateBadRequestErrorResponse,
104+
401: validateUnauthorizedErrorResponse,
105+
403: validateForbiddenErrorResponse,
106+
},
107+
authRequired: true,
108+
permissionsRequired: ['view-livechat-manager'], // is this permission appropriate for the targeted action?
109+
body: isPOSTLivechatSaveCustomFieldsParams,
101110
},
102-
authRequired: true,
103-
permissionsRequired: ['view-livechat-manager'], // is this permission appropriate for the targeted action?
104-
body: isPOSTLivechatRemoveCustomFields,
105-
},
106-
async function action() {
107-
const { customFieldId } = this.bodyParams;
111+
async function action() {
112+
const { customFieldId, customFieldData } = this.bodyParams;
108113

109-
const result = await LivechatCustomField.removeById(customFieldId);
110-
if (result.deletedCount === 0) {
111-
return API.v1.failure('Custom field not found');
112-
}
114+
if (!/^[0-9a-zA-Z-_]+$/.test(customFieldId)) {
115+
return API.v1.failure('Invalid custom field name. Use only letters, numbers, hyphens and underscores.');
116+
}
113117

114-
return API.v1.success();
115-
},
116-
);
118+
if (customFieldId) {
119+
const customField = await LivechatCustomField.findOneById(customFieldId);
120+
if (!customField) {
121+
return API.v1.failure('Custom Field Not found');
122+
}
123+
}
124+
125+
if (!customFieldId) {
126+
const customField = await LivechatCustomField.findOneById(customFieldData.field);
127+
if (customField) {
128+
return API.v1.failure('Custom Field already exists');
129+
}
130+
}
131+
132+
const { field, label, scope, visibility, ...extraData } = customFieldData;
133+
const result = await LivechatCustomField.createOrUpdateCustomField(customFieldId, field, label, scope, visibility, {
134+
...extraData,
135+
});
136+
137+
return API.v1.success({ customField: result });
138+
},
139+
)
140+
.post(
141+
'livechat/custom-fields.delete',
142+
{
143+
response: {
144+
200: POSTLivechatRemoveCustomFieldSuccess,
145+
400: validateBadRequestErrorResponse,
146+
401: validateUnauthorizedErrorResponse,
147+
403: validateForbiddenErrorResponse,
148+
},
149+
authRequired: true,
150+
permissionsRequired: ['view-livechat-manager'], // is this permission appropriate for the targeted action?
151+
body: isPOSTLivechatRemoveCustomFields,
152+
},
153+
async function action() {
154+
const { customFieldId } = this.bodyParams;
155+
156+
const result = await LivechatCustomField.removeById(customFieldId);
157+
if (result.deletedCount === 0) {
158+
return API.v1.failure('Custom field not found');
159+
}
160+
161+
return API.v1.success();
162+
},
163+
);
117164

118165
type LivechatCustomFieldsEndpoints = ExtractRoutesFromAPI<typeof livechatCustomFieldsEndpoints>;
119166

apps/meteor/app/livechat/server/methods/saveCustomField.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Match, check } from 'meteor/check';
55
import { Meteor } from 'meteor/meteor';
66

77
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
8+
import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
89

910
declare module '@rocket.chat/ddp-client' {
1011
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -25,6 +26,7 @@ declare module '@rocket.chat/ddp-client' {
2526

2627
Meteor.methods<ServerMethods>({
2728
async 'livechat:saveCustomField'(_id, customFieldData) {
29+
methodDeprecationLogger.method('livechat:saveCustomField', '8.0.0', '/v1/livechat/custom-fields.save');
2830
const uid = Meteor.userId();
2931
if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) {
3032
throw new Meteor.Error('error-not-allowed', 'Not allowed', {

apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
Box,
1515
} from '@rocket.chat/fuselage';
1616
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
17-
import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
17+
import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
1818
import { useQueryClient } from '@tanstack/react-query';
1919
import { useId, useMemo } from 'react';
2020
import { FormProvider, useForm, Controller } from 'react-hook-form';
@@ -72,13 +72,16 @@ const EditCustomFields = ({ customFieldData, onClose }: { customFieldData?: Seri
7272
formState: { isDirty, errors },
7373
} = methods;
7474

75-
const saveCustomField = useMethod('livechat:saveCustomField');
75+
const saveCustomField = useEndpoint('POST', '/v1/livechat/custom-fields.save');
7676

7777
const handleSave = useEffectEvent(async ({ visibility, ...data }: EditCustomFieldsFormData) => {
7878
try {
79-
await saveCustomField(customFieldData?._id as unknown as string, {
80-
visibility: visibility ? 'visible' : 'hidden',
81-
...data,
79+
await saveCustomField({
80+
customFieldId: customFieldData?._id as unknown as string,
81+
customFieldData: {
82+
visibility: visibility ? 'visible' : 'hidden',
83+
...data,
84+
},
8285
});
8386

8487
dispatchToastMessage({ type: 'success', message: t('Saved') });

apps/meteor/tests/e2e/utils/omnichannel/custom-field.ts

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,39 @@
11
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
22

3-
import { parseMeteorResponse } from '../parseMeteorResponse';
43
import type { BaseTest } from '../test';
54

65
type CustomField = Omit<ILivechatCustomField, '_id' | '_updatedAt'> & { field: string };
76

87
export const removeCustomField = (api: BaseTest['api'], id: string) => {
9-
return api.post('/method.call/livechat:deleteCustomField', {
10-
method: 'livechat:saveCustomField',
11-
params: [id],
12-
id: 'id',
13-
msg: 'method',
8+
return api.post('/livechat/custom-fields.delete', {
9+
customFieldId: id,
1410
});
1511
};
1612

17-
export const createCustomField = async (api: BaseTest['api'], overwrides: Partial<CustomField>) => {
18-
const response = await api.post('/method.call/livechat:saveCustomField', {
19-
message: JSON.stringify({
20-
method: 'livechat:saveCustomField',
21-
params: [
22-
null,
23-
{
24-
field: overwrides.field,
25-
label: overwrides.label || overwrides.field,
26-
visibility: 'visible',
27-
scope: 'visitor',
28-
searchable: false,
29-
regexp: '',
30-
type: 'input',
31-
required: false,
32-
defaultValue: '',
33-
options: '',
34-
public: false,
35-
...overwrides,
36-
},
37-
],
38-
id: 'id',
39-
msg: 'method',
40-
}),
13+
export const createCustomField = async (api: BaseTest['api'], overwrites: Partial<CustomField>) => {
14+
const response = await api.post('/livechat/custom-fields.save', {
15+
customFieldId: null,
16+
customFieldData: {
17+
field: overwrites.field,
18+
label: overwrites.label || overwrites.field,
19+
visibility: 'visible',
20+
scope: 'visitor',
21+
searchable: false,
22+
regexp: '',
23+
type: 'input',
24+
required: false,
25+
defaultValue: '',
26+
options: '',
27+
public: false,
28+
...overwrites,
29+
},
4130
});
4231

4332
if (!response.ok()) {
4433
throw new Error(`Failed to create custom field [http status: ${response.status()}]`);
4534
}
4635

47-
const customField = await parseMeteorResponse<CustomField & { _id: string }>(response);
36+
const { customField } = await response.json();
4837

4938
return {
5039
response,

apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,30 +2838,23 @@ describe('LIVECHAT - rooms', () => {
28382838
});
28392839
it('should throw an error if a valid custom field fails the check', async () => {
28402840
await request
2841-
.post(methodCall('livechat:saveCustomField'))
2841+
.post(api('livechat/custom-fields.save'))
28422842
.set(credentials)
28432843
.send({
2844-
message: JSON.stringify({
2845-
method: 'livechat:saveCustomField',
2846-
params: [
2847-
null,
2848-
{
2849-
field: 'intfield',
2850-
label: 'intfield',
2851-
scope: 'room',
2852-
visibility: 'visible',
2853-
regexp: '\\d+',
2854-
searchable: true,
2855-
type: 'input',
2856-
required: false,
2857-
defaultValue: '0',
2858-
options: '',
2859-
public: false,
2860-
},
2861-
],
2862-
id: 'id',
2863-
msg: 'method',
2864-
}),
2844+
customFieldId: null,
2845+
customFieldData: {
2846+
field: 'intfield',
2847+
label: 'intfield',
2848+
scope: 'room',
2849+
visibility: 'visible',
2850+
regexp: '\\d+',
2851+
searchable: true,
2852+
type: 'input',
2853+
required: false,
2854+
defaultValue: '0',
2855+
options: '',
2856+
public: false,
2857+
},
28652858
})
28662859
.expect(200);
28672860

0 commit comments

Comments
 (0)