Skip to content
7 changes: 7 additions & 0 deletions .changeset/rich-steaks-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/model-typings": patch
"@rocket.chat/models": patch
---

Fixed contacts being marked as `known` after editing a custom field, or resolving conflicts by adding a new model function that only updates the `customFields` or `conflictingFields` prop.
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/lib/custom-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function updateContactsCustomFields(contact: ILivechatContact, key:
contact.conflictingFields.push({ field: `customFields.${key}`, value });
}

await LivechatContacts.updateContact(contact._id, {
await LivechatContacts.updateContactCustomFields(contact._id, {
...(shouldUpdateCustomFields && { customFields: contact.customFields }),
...(contact.conflictingFields && { conflictingFields: contact.conflictingFields }),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sinon from 'sinon';

const modelsMock = {
LivechatContacts: {
updateContact: sinon.stub(),
updateContactCustomFields: sinon.stub(),
},
};

Expand All @@ -15,7 +15,7 @@ const { updateContactsCustomFields } = proxyquire.noCallThru().load('../../../..

describe('[Custom Fields] updateContactsCustomFields', () => {
beforeEach(() => {
modelsMock.LivechatContacts.updateContact.reset();
modelsMock.LivechatContacts.updateContactCustomFields.reset();
});

it('should not add conflictingFields to the update data when its nullish', async () => {
Expand All @@ -26,13 +26,13 @@ describe('[Custom Fields] updateContactsCustomFields', () => {
},
};

modelsMock.LivechatContacts.updateContact.resolves({ ...contact, customFields: { customField: 'newValue' } });
modelsMock.LivechatContacts.updateContactCustomFields.resolves({ ...contact, customFields: { customField: 'newValue' } });

await updateContactsCustomFields(contact, 'customField', 'newValue', true);

expect(modelsMock.LivechatContacts.updateContact.calledOnce).to.be.true;
expect(modelsMock.LivechatContacts.updateContact.getCall(0).args[0]).to.be.equal('contactId');
expect(modelsMock.LivechatContacts.updateContact.getCall(0).args[1]).to.deep.equal({
expect(modelsMock.LivechatContacts.updateContactCustomFields.calledOnce).to.be.true;
expect(modelsMock.LivechatContacts.updateContactCustomFields.getCall(0).args[0]).to.be.equal('contactId');
expect(modelsMock.LivechatContacts.updateContactCustomFields.getCall(0).args[1]).to.deep.equal({
customFields: { customField: 'newValue' },
});
});
Expand All @@ -45,16 +45,16 @@ describe('[Custom Fields] updateContactsCustomFields', () => {
},
};

modelsMock.LivechatContacts.updateContact.resolves({
modelsMock.LivechatContacts.updateContactCustomFields.resolves({
...contact,
conflictingFields: [{ field: 'customFields.customField', value: 'newValue' }],
});

await updateContactsCustomFields(contact, 'customField', 'newValue', false);

expect(modelsMock.LivechatContacts.updateContact.calledOnce).to.be.true;
expect(modelsMock.LivechatContacts.updateContact.getCall(0).args[0]).to.be.equal('contactId');
expect(modelsMock.LivechatContacts.updateContact.getCall(0).args[1]).to.deep.equal({
expect(modelsMock.LivechatContacts.updateContactCustomFields.calledOnce).to.be.true;
expect(modelsMock.LivechatContacts.updateContactCustomFields.getCall(0).args[0]).to.be.equal('contactId');
expect(modelsMock.LivechatContacts.updateContactCustomFields.getCall(0).args[1]).to.deep.equal({
conflictingFields: [{ field: 'customFields.customField', value: 'newValue' }],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface ILivechatContactsModel extends IBaseModel<ILivechatContact> {
): Promise<ILivechatContact['_id']>;
updateContact(contactId: string, data: Partial<ILivechatContact>, options?: FindOneAndUpdateOptions): Promise<ILivechatContact>;
updateById(contactId: string, update: UpdateFilter<ILivechatContact>, options?: UpdateOptions): Promise<Document | UpdateResult>;
updateContactCustomFields(contactId: string, data: Partial<ILivechatContact>, options?: UpdateOptions): Promise<ILivechatContact | null>;
addChannel(contactId: string, channel: ILivechatContactChannel): Promise<void>;
findPaginatedContacts(
search: { searchText?: string; unknown?: boolean },
Expand Down
19 changes: 19 additions & 0 deletions packages/models/src/models/LivechatContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
AtLeast,
ILivechatContact,
ILivechatContactChannel,
ILivechatContactConflictingField,
ILivechatContactVisitorAssociation,
ILivechatVisitor,
RocketChatRecordDeleted,
Expand Down Expand Up @@ -126,6 +127,24 @@ export class LivechatContactsRaw extends BaseRaw<ILivechatContact> implements IL
return this.updateOne({ _id: contactId }, update, options);
}

async updateContactCustomFields(
contactId: string,
dataToUpdate: { customFields: Record<string, unknown>; conflictingFields: ILivechatContactConflictingField[] },
options?: FindOneAndUpdateOptions,
): Promise<ILivechatContact | null> {
if (!dataToUpdate.customFields && !dataToUpdate.conflictingFields) {
throw new Error('At least one of customFields or conflictingFields must be provided');
}

return this.findOneAndUpdate(
{ _id: contactId },
{
$set: { ...dataToUpdate },
},
{ returnDocument: 'after', ...options },
);
}

findPaginatedContacts(
search: { searchText?: string; unknown?: boolean },
options?: FindOptions,
Expand Down
Loading