Skip to content

fix: unable to clear contact manager field#38265

Open
abhinavkrin wants to merge 15 commits intodevelopfrom
fix/clear-contact-manager-field
Open

fix: unable to clear contact manager field#38265
abhinavkrin wants to merge 15 commits intodevelopfrom
fix/clear-contact-manager-field

Conversation

@abhinavkrin
Copy link
Member

@abhinavkrin abhinavkrin commented Jan 19, 2026

Proposed changes (including videos or screenshots)

This PR fixes an issue where the Contact Manager field could not be cleared (set to empty) for a contact, either via the UI or through the API.

The PR brings changes which will allow clearing the contactManager field by broviding an empty string for the 'contactManager' field in the payload for the endpoint omnichannel/contacts.update.

Issue(s)

Steps to test or reproduce

Further comments

SUP-954

Summary by CodeRabbit

  • Bug Fixes

    • Endpoints now allow clearing the contact manager field when an empty or missing value is provided (applies to update and conflict-resolution flows).
    • Improved error handling when a contact cannot be found during update/conflict resolution.
  • Tests

    • Added tests validating removal of contact manager via update and during conflict resolution, and expanded conflict-resolution scenarios.

@abhinavkrin abhinavkrin requested review from a team as code owners January 19, 2026 19:43
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 19, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.2.0, but it targets 8.1.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Jan 19, 2026

🦋 Changeset detected

Latest commit: cd06be0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 40 packages
Name Type
@rocket.chat/models Patch
@rocket.chat/model-typings Patch
@rocket.chat/meteor Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/ui-client Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/media-calls Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/apps Patch
@rocket.chat/network-broker Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/ui-voip Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/license Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Walkthrough

Replaces inline contact updates with a patch-style API (patchContact) that accepts { set?, unset? }, enabling explicit unsetting of fields (notably contactManager); updates models, typings, server handlers, helper, and tests; adds a changeset documenting the fix.

Changes

Cohort / File(s) Summary
Changeset
\.changeset/thick-ties-hunt.md
Adds changeset documenting patch-level bumps for packages and notes the fix allowing clearing contactManager.
Model typings
packages/model-typings/src/models/ILivechatContactsModel.ts
Replaces updateContact(...) signature with `patchContact(contactId, { set?: Partial; unset?: Partial<Record<keyof ILivechatContact, ''
Models implementation
packages/models/src/models/LivechatContacts.ts
Adds patchContact implementing { set, unset } handling: constructs $set (ensures unknown, derives preRegistration) and conditional $unset, runs findOneAndUpdate, returns updated contact or null.
Server handlers & helper
apps/meteor/app/livechat/server/lib/contacts/updateContact.ts, .../resolveContactConflicts.ts, .../patchContact.ts
Introduces patchContact helper; callers switched to { set, unset } payloads; presence-check for contactManager drives unsetting; throws error-contact-not-found when patch returns null.
Server unit tests
apps/meteor/app/livechat/server/lib/contacts/*.spec.ts
Replace updateContact stubs/usages with patchContact; adapt assertions to new payload shape; add tests covering clearing contactManager (empty string and undefined), conflict-resolution flows, and related counters.
End-to-end tests
apps/meteor/tests/end-to-end/api/livechat/contacts.ts
Adds E2E tests verifying clearing contactManager via omnichannel/contacts.update and during omnichannel/contacts.conflicts; asserts the field is removed from returned contact.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (UI/API)
    participant Server as Omnichannel API
    participant Helper as patchContact (server helper)
    participant Model as LivechatContacts.model
    participant DB as MongoDB
    participant Post as Post-update flows

    rect rgba(200,220,255,0.5)
    Client->>Server: POST omnichannel/contacts.update (contactId, contactManager: "")
    end

    rect rgba(200,255,200,0.5)
    Server->>Helper: patchContact(contactId, { set: {...}, unset?: { contactManager: '' } })
    Helper->>Model: LivechatContacts.patchContact(contactId, { set, unset })
    Model->>DB: findOneAndUpdate({ _id, enabled: { $ne: false } }, { $set, $unset? })
    DB-->>Model: updatedContact | null
    Model-->>Helper: updatedContact | null
    Helper-->>Server: updatedContact | null
    end

    rect rgba(255,230,200,0.5)
    Server->>Post: trigger name/room/subscription/inquiry updates using updatedContact
    Server-->>Client: HTTP 200 { success: true, contact: updatedContact }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped to the DB with a nimble patch,
Cleared a manager with a tiny scratch.
Set fields, unset others, then scurried back,
Tests all green along the rabbit track. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: unable to clear contact manager field' directly describes the main change: enabling the ability to clear the contact manager field, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR implements the core requirement from SUP-954: enabling users to clear the Contact Manager field via the omnichannel/contacts.update API endpoint by accepting empty string values.
Out of Scope Changes check ✅ Passed All changes are focused on enabling the ability to clear the contactManager field through a new patchContact method with set/unset semantics, directly addressing the linked issue requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/clear-contact-manager-field

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@abhinavkrin abhinavkrin changed the title Fix/clear contact manager field fix: unable to clear contact manager field Jan 19, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/models/src/models/LivechatContacts.ts (1)

117-133: Remove the inline comment on line 122 to follow coding guidelines.

Per the coding guidelines for TypeScript files, avoid code comments in implementation. The comment explaining the contactManager unsetting intent is unnecessary—the code logic is clear from the conditional check and operation.

The MongoDB behavior regarding empty $unset objects is handled correctly for the driver versions in use (6.10.0+). However, the main issue is the inline comment that violates the stated guideline.

🧹 Nitpick comments (1)
apps/meteor/app/livechat/server/lib/contacts/updateContact.ts (1)

74-82: Logic is correct for enabling contactManager clearing.

The use of 'contactManager' in params correctly detects key presence regardless of value, enabling the model layer to handle empty strings as unset operations. The validation at lines 39-41 appropriately only triggers for truthy values.

However, as per coding guidelines for TypeScript files, consider removing the inline comment on line 78. The behavior is self-documenting through the 'in' operator usage, and the model layer's handling can be documented in the model itself if needed.

🔧 Suggested change
 	const updatedContact = await LivechatContacts.updateContact(contactId, {
 		name,
 		...(emails && { emails: emails?.map((address) => ({ address })) }),
 		...(phones && { phones: phones?.map((phoneNumber) => ({ phoneNumber })) }),
-		...('contactManager' in params && { contactManager }), // A contactManager of empty string or undefined will be unset in the model method
+		...('contactManager' in params && { contactManager }),
 		...(channels && { channels }),
 		...(customFieldsToUpdate && { customFields: customFieldsToUpdate }),
 		...(wipeConflicts && { conflictingFields: [] }),
 	});

@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 90.24390% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.41%. Comparing base (75d089c) to head (cd06be0).
⚠️ Report is 23 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38265      +/-   ##
===========================================
- Coverage    70.76%   70.41%   -0.36%     
===========================================
  Files         3159     3162       +3     
  Lines       109397   110698    +1301     
  Branches     19657    19925     +268     
===========================================
+ Hits         77416    77944     +528     
- Misses       29950    30724     +774     
+ Partials      2031     2030       -1     
Flag Coverage Δ
e2e 60.39% <ø> (+0.08%) ⬆️
e2e-api 47.80% <ø> (-0.14%) ⬇️
unit 71.40% <90.24%> (-0.55%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2026

📦 Docker Image Size Report

➡️ Changes

Service Current Baseline Change Percent
sum of all images 0B 0B 0B
account-service 0B 0B 0B
authorization-service 0B 0B 0B
ddp-streamer-service 0B 0B 0B
omnichannel-transcript-service 0B 0B 0B
presence-service 0B 0B 0B
queue-worker-service 0B 0B 0B
rocketchat 0B 0B 0B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/18 22:53", "11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "02/05 09:51 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.00]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.00]
  line "queue-worker-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "rocketchat" [0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.00]
Loading

Statistics (last 30 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.4GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 0B
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-38265
  • Baseline: develop
  • Timestamp: 2026-02-05 09:51:20 UTC
  • Historical data points: 30

Updated: Thu, 05 Feb 2026 09:51:21 GMT

Copy link
Contributor

@ricardogarim ricardogarim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, might the same issue exist in resolveContactConflicts.ts for the omnichannel/contacts.conflicts endpoint? It also uses a truthy check which would prevent clearing the contactManager field.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/app/livechat/server/lib/contacts/resolveContactConflicts.ts (1)

46-69: Contact-manager conflicts won’t clear when contactManager is emptied.

Clearing contactManager now uses unset, but fieldsToRemove only removes manager when contactManager is truthy. With an empty string, the conflict remains. Align this with the same condition used for unset.

🐛 Proposed fix
-		const fieldsToRemove = new Set<string>(
-			[
-				name && 'name',
-				contactManager && 'manager',
+		const fieldsToRemove = new Set<string>(
+			[
+				name && 'name',
+				('contactManager' in params) && 'manager',
 				...(customFields ? Object.keys(customFields).map((key) => `customFields.${key}`) : []),
 			].filter((field): field is string => !!field),
 		);
🤖 Fix all issues with AI agents
In `@apps/meteor/app/livechat/server/lib/contacts/updateContact.ts`:
- Around line 74-85: The patchContact call is including name even when
undefined, which can overwrite existing names; update the
LivechatContacts.patchContact invocation in updateContact.ts to only add name to
the set object when params.name is explicitly provided (e.g., check for
params.hasOwnProperty('name') or typeof name !== 'undefined') so that the set
payload omits name when not present; keep the other conditional spreads (emails,
phones, contactManager, channels, customFieldsToUpdate, wipeConflicts) as-is and
retain the existing unset logic for contactManager.
🧹 Nitpick comments (1)
apps/meteor/tests/end-to-end/api/livechat/contacts.ts (1)

795-836: Drop inline comments in test body.

Guideline asks to avoid code comments in implementation; these can be removed without losing intent.

♻️ Proposed cleanup
-			// Create a conflict
 			await request.post(api('livechat/custom.field')).send({ token, key: 'cf1', value: '111', overwrite: true }).expect(200);

 			await request.post(api('livechat/custom.field')).send({ token, key: 'cf1', value: '222', overwrite: false }).expect(200);

-			// Verify the contact has a contact manager and conflicts
 			const contactBefore = await request.get(api('omnichannel/contacts.get')).set(credentials).query({ contactId }).expect(200);
As per coding guidelines, avoid code comments in implementation.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/meteor/app/livechat/server/lib/contacts/updateContact.ts">

<violation number="1" location="apps/meteor/app/livechat/server/lib/contacts/updateContact.ts:76">
P2: Using a truthy check here skips updates when `name` is an empty string, but downstream logic still treats `name` as a change and updates rooms/subscriptions. This can leave the contact name unchanged while related data is cleared. Prefer checking for undefined so empty strings are persisted consistently.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@alfredodelfabro alfredodelfabro added this to the 8.2.0 milestone Jan 22, 2026
abhinavkrin and others added 10 commits January 28, 2026 18:07
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
@abhinavkrin abhinavkrin force-pushed the fix/clear-contact-manager-field branch from 264eaa1 to 2a05f70 Compare January 28, 2026 12:39
@abhinavkrin abhinavkrin requested a review from KevLehman January 28, 2026 12:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/app/livechat/server/lib/contacts/updateContact.spec.ts (1)

15-35: Align proxyquire stub path with the actual import specifier.

updateContact.ts imports from './patchContact' (no extension), but the test stubs './patchContact.ts' (with extension). The proxyquire specifiers must match exactly; a mismatch causes the stub to be ignored.

✅ Proposed fix
-const { patchContact } = proxyquire.noCallThru().load('./patchContact.ts', {
+const { patchContact } = proxyquire.noCallThru().load('./patchContact', {
 	'@rocket.chat/models': modelsMock,
 });

 ...
-	'./patchContact.ts': {
+	'./patchContact': {
 		patchContact,
 	},
🤖 Fix all issues with AI agents
In
`@apps/meteor/app/livechat/server/lib/contacts/resolveContactConflicts.spec.ts`:
- Around line 18-30: The proxyquire stubs use a different module specifier than
production so the dependency isn't intercepted: change both occurrences where
proxyquire.noCallThru().load is given './patchContact.ts' to use the exact same
specifier as the real import ('./patchContact') so the stub for patchContact
(used when loading resolveContactConflicts via resolveContactConflicts)
correctly overrides the module; update the string keys passed to proxyquire for
both the standalone patchContact load and the resolveContactConflicts stub
mapping.

In `@apps/meteor/app/livechat/server/lib/contacts/updateContact.spec.ts`:
- Around line 61-69: The test passes null for contactManager but the API
contract expects a string to clear the field; update the test call to
updateContact to use an empty string ('') for contactManager (reference the test
case invoking updateContact and the assertions on
modelsMock.LivechatContacts.patchContact) so the payload matches the params type
and represents the intended “clear” value.

Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants