From 5e78306af121ae603ed87bc9e54e2415e228adc1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 4 Mar 2024 14:22:45 -0600 Subject: [PATCH 1/8] Refactor a bit custom field handling on livechat/visitor endpoint + log cf that were not processed --- .../app/livechat/server/api/v1/visitor.ts | 47 +++++++++---- .../server/models/raw/LivechatCustomField.ts | 10 ++- .../end-to-end/api/livechat/09-visitors.ts | 66 +++++++++++++++++++ .../src/models/ILivechatCustomFieldModel.ts | 5 ++ 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index 3d78280c51093..c95de5787c59a 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, IRoom } from '@rocket.chat/core-typings'; +import type { ILivechatCustomField, ILivechatVisitor, IRoom } from '@rocket.chat/core-typings'; import { LivechatVisitors as VisitorsRaw, LivechatCustomField, LivechatRooms } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -68,16 +68,41 @@ API.v1.addRoute('livechat/visitor', { ); } - if (customFields && Array.isArray(customFields)) { - for await (const field of customFields) { - const customField = await LivechatCustomField.findOneById(field.key); - if (!customField) { - continue; - } - const { key, value, overwrite } = field; - if (customField.scope === 'visitor' && !(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { - return API.v1.failure(); - } + if (customFields && Array.isArray(customFields) && customFields.length > 0) { + const keys = customFields.map((field) => field.key); + const errors: string[] = []; + + const processedKeys = await Promise.all( + await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { + projection: { _id: 1 }, + }) + .map(async (field) => { + const customField = customFields.find((f) => f.key === field._id); + if (!customField) { + return; + } + + const { key, value, overwrite } = customField; + // TODO: Change this to Bulk update + if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { + errors.push(key); + } + + return key; + }) + .toArray(), + ); + + if (processedKeys.length !== keys.length) { + LivechatTyped.logger.warn({ + msg: 'Some custom fields were not processed', + visitorId, + missingKeys: keys.filter((key) => !processedKeys.includes(key)), + }); + } + + if (errors.length > 0) { + throw new Meteor.Error('error-updating-custom-fields', `Error updating custom fields: ${errors.join(', ')}`); } visitor = await VisitorsRaw.findOneEnabledById(visitorId, {}); diff --git a/apps/meteor/server/models/raw/LivechatCustomField.ts b/apps/meteor/server/models/raw/LivechatCustomField.ts index 46bc32c52177c..71228f55069d0 100644 --- a/apps/meteor/server/models/raw/LivechatCustomField.ts +++ b/apps/meteor/server/models/raw/LivechatCustomField.ts @@ -1,6 +1,6 @@ import type { ILivechatCustomField, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { ILivechatCustomFieldModel } from '@rocket.chat/model-typings'; -import type { Db, Collection, IndexDescription, FindOptions, FindCursor } from 'mongodb'; +import type { Db, Collection, IndexDescription, FindOptions, FindCursor, Document } from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -73,4 +73,12 @@ export class LivechatCustomFieldRaw extends BaseRaw implem return record; } + + findByIdsAndScope( + ids: ILivechatCustomField['_id'][], + scope: ILivechatCustomField['scope'], + options?: FindOptions, + ): FindCursor { + return this.find({ _id: { $in: ids }, scope }, options); + } } diff --git a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts index 372f7ddf5d7b4..a5518486b1403 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts @@ -114,6 +114,72 @@ describe('LIVECHAT - visitors', function () { expect(body2.visitor).to.have.property('phone'); expect(body2.visitor.phone[0].phoneNumber).to.equal(phone); }); + it('should update a visitor custom fields when customFields key is provided', async () => { + const token = `${new Date().getTime()}-test`; + const customFieldName = `new_custom_field_${Date.now()}`; + await createCustomField({ + searchable: true, + field: customFieldName, + label: customFieldName, + defaultValue: 'test_default_address', + scope: 'visitor', + visibility: 'public', + regexp: '', + }); + const { body } = await request.post(api('livechat/visitor')).send({ + visitor: { + token, + customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }], + }, + }); + + expect(body).to.have.property('success', true); + expect(body).to.have.property('visitor'); + expect(body.visitor).to.have.property('token', token); + expect(body.visitor).to.have.property('livechatData'); + expect(body.visitor.livechatData).to.have.property(customFieldName, 'Not a real address :)'); + }); + + it('should not update a custom field when it does not exists', async () => { + const token = `${new Date().getTime()}-test`; + const customFieldName = `new_custom_field_${Date.now()}`; + const { body } = await request.post(api('livechat/visitor')).send({ + visitor: { + token, + customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }], + }, + }); + + expect(body).to.have.property('success', true); + expect(body).to.have.property('visitor'); + expect(body.visitor).to.have.property('token', token); + expect(body.visitor).to.not.have.property('livechatData'); + }); + + it('should not update a custom field when the scope of it is not visitor', async () => { + const token = `${new Date().getTime()}-test`; + const customFieldName = `new_custom_field_${Date.now()}`; + await createCustomField({ + searchable: true, + field: customFieldName, + label: customFieldName, + defaultValue: 'test_default_address', + scope: 'room', + visibility: 'public', + regexp: '', + }); + const { body } = await request.post(api('livechat/visitor')).send({ + visitor: { + token, + customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }], + }, + }); + + expect(body).to.have.property('success', true); + expect(body).to.have.property('visitor'); + expect(body.visitor).to.have.property('token', token); + expect(body.visitor).to.not.have.property('livechatData'); + }); }); describe('livechat/visitors.info', () => { diff --git a/packages/model-typings/src/models/ILivechatCustomFieldModel.ts b/packages/model-typings/src/models/ILivechatCustomFieldModel.ts index 856d39c2dd4d0..06b5ac1450275 100644 --- a/packages/model-typings/src/models/ILivechatCustomFieldModel.ts +++ b/packages/model-typings/src/models/ILivechatCustomFieldModel.ts @@ -34,4 +34,9 @@ export interface ILivechatCustomFieldModel extends IBaseModel; + findByIdsAndScope( + ids: ILivechatCustomField['_id'][], + scope: ILivechatCustomField['scope'], + options?: FindOptions, + ): FindCursor; } From 9eef8c67fd8672df7024469181b38b44ec6fd237 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 4 Mar 2024 14:23:57 -0600 Subject: [PATCH 2/8] Create happy-pillows-call.md --- .changeset/happy-pillows-call.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/happy-pillows-call.md diff --git a/.changeset/happy-pillows-call.md b/.changeset/happy-pillows-call.md new file mode 100644 index 0000000000000..da1713cc5bf96 --- /dev/null +++ b/.changeset/happy-pillows-call.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +fix: `livechat/visitor` not updating custom fields some times From 61440ee1329f2515ab66f91be8f036c549692136 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 6 Mar 2024 12:05:41 -0600 Subject: [PATCH 3/8] Update happy-pillows-call.md --- .changeset/happy-pillows-call.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-pillows-call.md b/.changeset/happy-pillows-call.md index da1713cc5bf96..9100e1f7fe1b5 100644 --- a/.changeset/happy-pillows-call.md +++ b/.changeset/happy-pillows-call.md @@ -3,4 +3,4 @@ "@rocket.chat/model-typings": patch --- -fix: `livechat/visitor` not updating custom fields some times +Changed logic that process custom fields from visitors when updating its data, making the process more reliable and faster. From 887fa9f277c052d9e93883aa7d41445feac3d60d Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 11 Mar 2024 07:52:03 -0600 Subject: [PATCH 4/8] Update visitor.ts --- apps/meteor/app/livechat/server/api/v1/visitor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index c95de5787c59a..9ecb852af391e 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -102,7 +102,8 @@ API.v1.addRoute('livechat/visitor', { } if (errors.length > 0) { - throw new Meteor.Error('error-updating-custom-fields', `Error updating custom fields: ${errors.join(', ')}`); + LivechatTyped.logger.error({ msg: 'Error updating custom fields', err: errors }); + throw new Error('error-updating-custom-fields'); } visitor = await VisitorsRaw.findOneEnabledById(visitorId, {}); From 1bb15ce4ea3340c7e16f574f18e28fbff2adef57 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 13 Mar 2024 07:46:19 -0600 Subject: [PATCH 5/8] he --- .../app/livechat/server/api/v1/visitor.ts | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index c95de5787c59a..c223625b7b6f0 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -72,26 +72,24 @@ API.v1.addRoute('livechat/visitor', { const keys = customFields.map((field) => field.key); const errors: string[] = []; - const processedKeys = await Promise.all( - await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { - projection: { _id: 1 }, + const processedKeys = await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { + projection: { _id: 1 }, + }) + .map(async (field) => { + const customField = customFields.find((f) => f.key === field._id); + if (!customField) { + return; + } + + const { key, value, overwrite } = customField; + // TODO: Change this to Bulk update + if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { + errors.push(key); + } + + return key; }) - .map(async (field) => { - const customField = customFields.find((f) => f.key === field._id); - if (!customField) { - return; - } - - const { key, value, overwrite } = customField; - // TODO: Change this to Bulk update - if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { - errors.push(key); - } - - return key; - }) - .toArray(), - ); + .toArray(); if (processedKeys.length !== keys.length) { LivechatTyped.logger.warn({ From 9962d5aff7e2356ecbba59d1c4ef0165958b4461 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 13 Mar 2024 08:14:31 -0600 Subject: [PATCH 6/8] i knew --- .../app/livechat/server/api/v1/visitor.ts | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index 2b2fb5ef253e5..c95de5787c59a 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -72,24 +72,26 @@ API.v1.addRoute('livechat/visitor', { const keys = customFields.map((field) => field.key); const errors: string[] = []; - const processedKeys = await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { - projection: { _id: 1 }, - }) - .map(async (field) => { - const customField = customFields.find((f) => f.key === field._id); - if (!customField) { - return; - } - - const { key, value, overwrite } = customField; - // TODO: Change this to Bulk update - if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { - errors.push(key); - } - - return key; + const processedKeys = await Promise.all( + await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { + projection: { _id: 1 }, }) - .toArray(); + .map(async (field) => { + const customField = customFields.find((f) => f.key === field._id); + if (!customField) { + return; + } + + const { key, value, overwrite } = customField; + // TODO: Change this to Bulk update + if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { + errors.push(key); + } + + return key; + }) + .toArray(), + ); if (processedKeys.length !== keys.length) { LivechatTyped.logger.warn({ @@ -100,8 +102,7 @@ API.v1.addRoute('livechat/visitor', { } if (errors.length > 0) { - LivechatTyped.logger.error({ msg: 'Error updating custom fields', err: errors }); - throw new Error('error-updating-custom-fields'); + throw new Meteor.Error('error-updating-custom-fields', `Error updating custom fields: ${errors.join(', ')}`); } visitor = await VisitorsRaw.findOneEnabledById(visitorId, {}); From 5f95d724d8266c5cce44f63f947dafbd22fcefa9 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 13 Mar 2024 10:36:26 -0600 Subject: [PATCH 7/8] error --- apps/meteor/app/livechat/server/api/v1/visitor.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index c95de5787c59a..9c19f5bbdec82 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -102,7 +102,12 @@ API.v1.addRoute('livechat/visitor', { } if (errors.length > 0) { - throw new Meteor.Error('error-updating-custom-fields', `Error updating custom fields: ${errors.join(', ')}`); + LivechatTyped.logger.error({ + msg: 'Error updating custom fields', + visitorId, + errors, + }); + throw new Error('error-updating-custom-fields'); } visitor = await VisitorsRaw.findOneEnabledById(visitorId, {}); From 6a3be13115d00e14b334321abdcde8563add6fda Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 15 Mar 2024 07:36:53 -0600 Subject: [PATCH 8/8] yet another test --- .../end-to-end/api/livechat/09-visitors.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts index a5518486b1403..ad441112af797 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts @@ -180,6 +180,39 @@ describe('LIVECHAT - visitors', function () { expect(body.visitor).to.have.property('token', token); expect(body.visitor).to.not.have.property('livechatData'); }); + + it('should not update a custom field whe the overwrite flag is false', async () => { + const token = `${new Date().getTime()}-test`; + const customFieldName = `new_custom_field_${Date.now()}`; + await createCustomField({ + searchable: true, + field: customFieldName, + label: customFieldName, + defaultValue: 'test_default_address', + scope: 'visitor', + visibility: 'public', + regexp: '', + }); + await request.post(api('livechat/visitor')).send({ + visitor: { + token, + customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }], + }, + }); + + const { body } = await request.post(api('livechat/visitor')).send({ + visitor: { + token, + customFields: [{ key: customFieldName, value: 'This should not change!', overwrite: false }], + }, + }); + + expect(body).to.have.property('success', true); + expect(body).to.have.property('visitor'); + expect(body.visitor).to.have.property('token', token); + expect(body.visitor).to.have.property('livechatData'); + expect(body.visitor.livechatData).to.have.property(customFieldName, 'Not a real address :)'); + }); }); describe('livechat/visitors.info', () => {