diff --git a/.changeset/warm-steaks-fetch.md b/.changeset/warm-steaks-fetch.md
new file mode 100644
index 0000000000000..842d7f04b46e6
--- /dev/null
+++ b/.changeset/warm-steaks-fetch.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Fixes contact's conflict resolution not working due to invalid parameters
diff --git a/apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ReviewContactModal.tsx b/apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ReviewContactModal.tsx
index bb5eacfe17d9a..7ca34aa3c4dc7 100644
--- a/apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ReviewContactModal.tsx
+++ b/apps/meteor/client/views/omnichannel/contactInfo/ContactInfo/ReviewContactModal.tsx
@@ -47,7 +47,7 @@ const ReviewContactModal = ({ contact, onCancel }: ReviewContactModalProps) => {
const payload = {
name,
contactManager,
- ...(customFields && { ...customFields }),
+ ...(customFields && { customFields }),
wipeConflicts: true,
};
@@ -86,7 +86,7 @@ const ReviewContactModal = ({ contact, onCancel }: ReviewContactModalProps) => {
return (
- {t(label as TranslationKey)}
+ {t(label as TranslationKey)}
{
rules={{
required: isContactManagerField ? undefined : t('Required_field', { field: t(label as TranslationKey) }),
}}
- render={({ field: { value, onChange } }) => }
+ render={({ field: { value, onChange } }) => (
+
+ )}
/>
-
+
{t('different_values_found', { number: values.length })}
- {errors?.[name] && {errors?.[name]?.message}}
+ {errors?.[name] && {errors?.[name]?.message}}
);
})}
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-conflict-review.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-conflict-review.spec.ts
new file mode 100644
index 0000000000000..f2845a1713195
--- /dev/null
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-conflict-review.spec.ts
@@ -0,0 +1,93 @@
+import { faker } from '@faker-js/faker';
+
+import { createFakeVisitor } from '../../mocks/data';
+import { IS_EE } from '../config/constants';
+import { Users } from '../fixtures/userStates';
+import { HomeOmnichannel } from '../page-objects';
+import { createCustomField } from '../utils/omnichannel/custom-field';
+import { createConversation } from '../utils/omnichannel/rooms';
+import { test, expect } from '../utils/test';
+
+const visitor = createFakeVisitor();
+
+test.skip(!IS_EE, 'Omnichannel Contact Review > Enterprise Only');
+
+test.use({ storageState: Users.user1.state });
+
+test.describe.serial('OC - Contact Review', () => {
+ let poHomeChannel: HomeOmnichannel;
+
+ const customFieldName = faker.string.uuid();
+ const visitorToken = faker.string.uuid();
+ let conversation: Awaited>;
+ let customField: Awaited>;
+
+ test.beforeAll(async ({ api }) => {
+ (
+ await Promise.all([
+ api.post('/livechat/users/agent', { username: 'user1' }),
+ api.post('/livechat/users/manager', { username: 'user1' }),
+ ])
+ ).every((res) => expect(res.status()).toBe(200));
+
+ customField = await createCustomField(api, { field: customFieldName });
+ });
+
+ test.beforeEach(async ({ page }) => {
+ poHomeChannel = new HomeOmnichannel(page);
+ });
+
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/');
+ await page.locator('.main-content').waitFor();
+ });
+
+ test.beforeEach(async ({ api }) => {
+ conversation = await createConversation(api, { visitorName: visitor.name, agentId: `user1`, visitorToken });
+ });
+
+ test.beforeEach(async ({ api }) => {
+ const resCustomFieldA = await api.post('/livechat/custom.field', {
+ token: visitorToken,
+ key: customFieldName,
+ value: 'custom-field-value',
+ overwrite: true,
+ });
+
+ expect(resCustomFieldA.status()).toBe(200);
+
+ const resCustomFieldB = await api.post('/livechat/custom.field', {
+ token: visitorToken,
+ key: customFieldName,
+ value: 'custom-field-value-2',
+ overwrite: false,
+ });
+
+ expect(resCustomFieldB.status()).toBe(200);
+ });
+
+ test.afterAll(async ({ api }) => {
+ (await Promise.all([api.delete('/livechat/users/agent/user1'), api.delete('/livechat/users/manager/user1')])).every((res) =>
+ expect(res.status()).toBe(200),
+ );
+
+ await conversation.delete();
+ await customField.delete();
+ });
+
+ test('OC - Contact Review - Update custom field conflicting', async ({ page }) => {
+ await poHomeChannel.sidenav.getSidebarItemByName(visitor.name).click();
+ await poHomeChannel.content.btnContactInformation.click();
+
+ await poHomeChannel.content.contactReviewModal.btnSeeConflicts.click();
+
+ await poHomeChannel.content.contactReviewModal.getFieldByName(customFieldName).click();
+ await poHomeChannel.content.contactReviewModal.findOption('custom-field-value-2').click();
+ await poHomeChannel.content.contactReviewModal.btnSave.click();
+
+ const response = await page.waitForResponse('**/api/v1/omnichannel/contacts.update');
+ await expect(response.status()).toBe(200);
+
+ await expect(poHomeChannel.content.contactReviewModal.btnSeeConflicts).not.toBeVisible();
+ });
+});
diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts
index d6c8cf9decf72..335cec483cb19 100644
--- a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts
+++ b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts
@@ -3,16 +3,20 @@ import type { Locator, Page } from '@playwright/test';
import { OmnichannelTransferChatModal } from '../omnichannel-transfer-chat-modal';
import { HomeContent } from './home-content';
import { OmnichannelCloseChatModal } from './omnichannel-close-chat-modal';
+import { OmnichannelContactReviewModal } from '../omnichannel-contact-review-modal';
export class HomeOmnichannelContent extends HomeContent {
readonly closeChatModal: OmnichannelCloseChatModal;
readonly forwardChatModal: OmnichannelTransferChatModal;
+ readonly contactReviewModal: OmnichannelContactReviewModal;
+
constructor(page: Page) {
super(page);
this.closeChatModal = new OmnichannelCloseChatModal(page);
this.forwardChatModal = new OmnichannelTransferChatModal(page);
+ this.contactReviewModal = new OmnichannelContactReviewModal(page);
}
get btnReturnToQueue(): Locator {
diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-contact-review-modal.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-contact-review-modal.ts
new file mode 100644
index 0000000000000..520faa2891ada
--- /dev/null
+++ b/apps/meteor/tests/e2e/page-objects/omnichannel-contact-review-modal.ts
@@ -0,0 +1,25 @@
+import type { Locator, Page } from '@playwright/test';
+
+export class OmnichannelContactReviewModal {
+ private readonly page: Page;
+
+ constructor(page: Page) {
+ this.page = page;
+ }
+
+ get btnSeeConflicts(): Locator {
+ return this.page.getByRole('button', { name: 'See conflicts', exact: true });
+ }
+
+ get btnSave(): Locator {
+ return this.page.getByRole('button', { name: 'Save', exact: true });
+ }
+
+ getFieldByName(name: string): Locator {
+ return this.page.getByLabel(name, { exact: true });
+ }
+
+ findOption(name: string): Locator {
+ return this.page.getByRole('option', { name, exact: true });
+ }
+}
diff --git a/apps/meteor/tests/e2e/utils/omnichannel/custom-field.ts b/apps/meteor/tests/e2e/utils/omnichannel/custom-field.ts
new file mode 100644
index 0000000000000..bc476aae918c8
--- /dev/null
+++ b/apps/meteor/tests/e2e/utils/omnichannel/custom-field.ts
@@ -0,0 +1,54 @@
+import type { ILivechatCustomField } from '@rocket.chat/core-typings';
+
+import { parseMeteorResponse } from '../parseMeteorResponse';
+import type { BaseTest } from '../test';
+
+type CustomField = Omit & { field: string };
+
+export const removeCustomField = (api: BaseTest['api'], id: string) => {
+ return api.post('/method.call/livechat:deleteCustomField', {
+ method: 'livechat:saveCustomField',
+ params: [id],
+ id: 'id',
+ msg: 'method',
+ });
+};
+
+export const createCustomField = async (api: BaseTest['api'], overwrides: Partial) => {
+ const response = await api.post('/method.call/livechat:saveCustomField', {
+ message: JSON.stringify({
+ method: 'livechat:saveCustomField',
+ params: [
+ null,
+ {
+ field: overwrides.field,
+ label: overwrides.label || overwrides.field,
+ visibility: 'visible',
+ scope: 'visitor',
+ searchable: false,
+ regexp: '',
+ type: 'input',
+ required: false,
+ defaultValue: '',
+ options: '',
+ public: false,
+ ...overwrides,
+ },
+ ],
+ id: 'id',
+ msg: 'method',
+ }),
+ });
+
+ if (!response.ok()) {
+ throw new Error(`Failed to create custom field [http status: ${response.status()}]`);
+ }
+
+ const customField = await parseMeteorResponse(response);
+
+ return {
+ response,
+ customField,
+ delete: () => removeCustomField(api, customField._id),
+ };
+};