Skip to content

Commit

Permalink
fix(Livechat): Restore missing setDepartment livechat API endpoint fu…
Browse files Browse the repository at this point in the history
…nctionality (#32626)
  • Loading branch information
MartinSchoeler authored Jun 26, 2024
1 parent c3489db commit dbc79b7
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 90 deletions.
10 changes: 10 additions & 0 deletions .changeset/funny-snails-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/livechat": patch
---

livechat `setDepartment` livechat api fixes:
- Changing department didn't reflect on the registration form in real time
- Changing the department mid conversation didn't transfer the chat
- Depending on the state of the department, it couldn't be set as default

116 changes: 84 additions & 32 deletions apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ test.describe('OC - Livechat API', () => {
test.skip(!IS_EE, 'Enterprise Only');
// Tests that requires interaction from an agent or more
let poAuxContext: { page: Page; poHomeOmnichannel: HomeOmnichannel };
let poAuxContext2: { page: Page; poHomeOmnichannel: HomeOmnichannel };
let poLiveChat: OmnichannelLiveChatEmbedded;
let page: Page;
let agent: Awaited<ReturnType<typeof createAgent>>;
Expand Down Expand Up @@ -247,18 +246,12 @@ test.describe('OC - Livechat API', () => {
await poAuxContext.poHomeOmnichannel.sidenav.switchStatus('online');
}

if (testInfo.title === 'OC - Livechat API - setDepartment') {
const { page: pageCtx2 } = await createAuxContext(browser, Users.user2);
poAuxContext2 = { page: pageCtx2, poHomeOmnichannel: new HomeOmnichannel(pageCtx) };
}

await page.goto('/packages/rocketchat_livechat/assets/demo.html');
});

test.afterEach(async () => {
await poAuxContext.page.close();
await page.close();
await poAuxContext2?.page.close();
await pageContext?.close();
});

Expand Down Expand Up @@ -316,41 +309,100 @@ test.describe('OC - Livechat API', () => {
});
});

test('OC - Livechat API - setDepartment', async () => {
const [departmentA, departmentB] = departments.map(({ data }) => data);
const registerGuestVisitor = {
name: faker.person.firstName(),
email: faker.internet.email(),
token: faker.string.uuid(),
department: departmentA._id,
};
test.describe('OC - Livechat API - setDepartment', () => {
let poAuxContext2: { page: Page; poHomeOmnichannel: HomeOmnichannel };

// Start Chat
await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget());
await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
test.beforeEach(async ({ browser }) => {
const { page: pageCtx2 } = await createAuxContext(browser, Users.user2);
poAuxContext2 = { page: pageCtx2, poHomeOmnichannel: new HomeOmnichannel(pageCtx2) };
});

await poLiveChat.page.evaluate(
(registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
registerGuestVisitor,
);
test.afterEach(async () => {
await poAuxContext2.page.close();
});

await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
test('setDepartment - Called during ongoing conversation', async () => {
const [departmentA, departmentB] = departments.map(({ data }) => data);
const registerGuestVisitor = {
name: faker.person.firstName(),
email: faker.internet.email(),
token: faker.string.uuid(),
department: departmentA._id,
};

await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor');
await poLiveChat.btnSendMessageToOnlineAgent.click();
// Start Chat
await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget());
await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();

await test.step('Expect registered guest to be in dep1', async () => {
await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name);
await poLiveChat.page.evaluate(
(registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
registerGuestVisitor,
);

await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();

await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor');
await poLiveChat.btnSendMessageToOnlineAgent.click();

await test.step('Expect registered guest to be in dep1', async () => {
await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name);
await expect(poAuxContext.poHomeOmnichannel.content.channelHeader).toContainText(registerGuestVisitor.name);
});

const depId = departmentB._id;

await test.step('Expect chat not be transferred', async () => {
await poLiveChat.page.evaluate((depId) => window.RocketChat.livechat.setDepartment(depId), depId);

await poAuxContext2.page.locator('role=navigation >> role=button[name=Search]').click();
await poAuxContext2.page.locator('role=search >> role=searchbox').fill(registerGuestVisitor.name);
await expect(
poAuxContext2.page.locator(`role=search >> role=listbox >> role=link >> text="${registerGuestVisitor.name}"`),
).not.toBeVisible();
});

await test.step('Expect registered guest to still be in dep1', async () => {
await poAuxContext.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name);
await expect(poAuxContext.poHomeOmnichannel.content.channelHeader).toContainText(registerGuestVisitor.name);
});
});

const depId = departmentB._id;
test('setDepartment - Called before conversation', async () => {
const departmentB = departments[1].data;
const registerGuestVisitor = {
name: faker.person.firstName(),
email: faker.internet.email(),
};

const depId = departmentB._id;

await test.step('Expect setDepartment to change a guest department', async () => {
await poLiveChat.page.evaluate((depId) => window.RocketChat.livechat.setDepartment(depId), depId);
});

await test.step('Expect registered guest to be in dep2', async () => {
await poAuxContext2.poHomeOmnichannel.sidenav.openChat(registerGuestVisitor.name);
await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget());
await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();

await poLiveChat.sendMessage(registerGuestVisitor, false);

await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor');
await poLiveChat.btnSendMessageToOnlineAgent.click();

await test.step('Expect registered guest to be in dep2', async () => {
await poAuxContext2.page.locator('role=navigation >> role=button[name=Search]').click();
await poAuxContext2.page.locator('role=search >> role=searchbox').fill(registerGuestVisitor.name);
await poAuxContext2.page.locator(`role=search >> role=listbox >> role=link >> text="${registerGuestVisitor.name}"`).click();
await poAuxContext2.page.locator('role=main').waitFor();
await poAuxContext2.page.locator('role=main >> role=heading[level=1]').waitFor();
await expect(poAuxContext2.page.locator('role=main >> .rcx-skeleton')).toHaveCount(0);
await expect(poAuxContext2.page.locator('role=main >> role=list')).not.toHaveAttribute('aria-busy', 'true');
});

await test.step('Expect registered guest not to be in dep1', async () => {
await poAuxContext.page.locator('role=navigation >> role=button[name=Search]').click();
await poAuxContext.page.locator('role=search >> role=searchbox').fill(registerGuestVisitor.name);
await expect(
poAuxContext.page.locator(`role=search >> role=listbox >> role=link >> text="${registerGuestVisitor.name}"`),
).not.toBeVisible();
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ export class OmnichannelLiveChatEmbedded {

public async sendMessage(liveChatUser: { name: string; email: string }, isOffline = true): Promise<void> {
const buttonLabel = isOffline ? 'Send' : 'Start chat';
await this.inputName.type(liveChatUser.name);
await this.inputEmail.type(liveChatUser.email);
await this.inputName.fill(liveChatUser.name);
await this.inputEmail.fill(liveChatUser.email);
if (isOffline) {
await this.textAreaMessage.type('any_message');
await this.textAreaMessage.fill('any_message');
await this.btnSendMessage(buttonLabel).click();
return this.btnFinishOfflineMessage().click();
}
Expand Down
3 changes: 2 additions & 1 deletion packages/livechat/src/components/Form/CustomFields/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Control, FieldErrors, FieldValues } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import type { RegisterFormValues } from '../../../routes/Register';
import { FormField } from '../FormField';
import { SelectInput } from '../SelectInput';
import { TextInput } from '../TextInput';
Expand All @@ -19,7 +20,7 @@ export type CustomField = {
type RenderCustomFieldsProps = {
customFields: CustomField[];
loading: boolean;
control: Control;
control: Control<RegisterFormValues>;
errors: FieldErrors<FieldValues>;
};

Expand Down
19 changes: 3 additions & 16 deletions packages/livechat/src/components/Form/SelectInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ComponentChild, Ref } from 'preact';
import type { TargetedEvent } from 'preact/compat';
import { useState } from 'preact/hooks';
import type { JSXInternal } from 'preact/src/jsx';

import { createClassName } from '../../../helpers/createClassName';
Expand Down Expand Up @@ -38,38 +37,26 @@ export const SelectInput = ({
value,
ref,
}: SelectInputProps) => {
const [internalValue, setInternalValue] = useState(value);

const SelectOptions = Array.from(options).map(({ value, label }, key) => (
<option key={key} value={value} className={createClassName(styles, 'select-input__option')}>
{label}
</option>
));

const handleChange = (event: TargetedEvent<HTMLSelectElement, Event>) => {
onChange(event);

if (event.defaultPrevented) {
return;
}

setInternalValue((event.target as HTMLSelectElement)?.value);
};

return (
<div className={createClassName(styles, 'select-input', {}, [className])} style={style}>
<select
name={name}
value={internalValue}
value={value}
disabled={disabled}
onChange={handleChange}
onChange={onChange}
onBlur={onBlur}
onInput={onInput}
className={createClassName(styles, 'select-input__select', {
disabled,
error,
small,
placeholder: !internalValue,
placeholder: !value,
})}
ref={ref}
>
Expand Down
35 changes: 14 additions & 21 deletions packages/livechat/src/lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const evaluateChangesAndLoadConfigByFields = async (fn: () => Promise<void>) =>
const oldStore = JSON.parse(
JSON.stringify({
user: store.state.user || {},
department: store.state.department,
token: store.state.token,
}),
);
Expand All @@ -37,12 +36,6 @@ const evaluateChangesAndLoadConfigByFields = async (fn: () => Promise<void>) =>
return;
}

if (oldStore.department !== store.state.department) {
await loadConfig();
await loadMessages();
return;
}

if (oldStore.token !== store.state.token) {
await loadConfig();
await loadMessages();
Expand All @@ -56,6 +49,14 @@ const createOrUpdateGuest = async (guest: StoreState['guest']) => {

const { token } = guest;
token && (await store.setState({ token }));

const {
iframe: { defaultDepartment },
} = store.state;
if (defaultDepartment && !guest.department) {
guest.department = defaultDepartment;
}

const { visitor: user } = await Livechat.grantVisitor({ visitor: { ...guest } });

if (!user) {
Expand Down Expand Up @@ -135,21 +136,11 @@ const api = {
},

setDepartment: async (value: string) => {
await evaluateChangesAndLoadConfigByFields(async () => api._setDepartment(value));
},

_setDepartment: async (value: string) => {
const {
user,
config: { departments = [] },
defaultAgent,
} = store.state;

if (!user) {
updateIframeData({ defaultDepartment: value });
return;
}

const department = departments.find((dep) => dep._id === value || dep.name === value)?._id || '';

if (!department) {
Expand All @@ -158,8 +149,7 @@ const api = {
);
}

updateIframeGuestData({ department });
store.setState({ department });
updateIframeData({ defaultDepartment: department });

if (defaultAgent && defaultAgent.department !== department) {
store.setState({ defaultAgent: undefined });
Expand Down Expand Up @@ -243,9 +233,12 @@ const api = {
if (!data.token) {
data.token = createToken();
}
const {
iframe: { defaultDepartment },
} = store.state;

if (data.department) {
await api._setDepartment(data.department);
if (defaultDepartment && !data.department) {
data.department = defaultDepartment;
}

Livechat.unsubscribeAll();
Expand Down
8 changes: 8 additions & 0 deletions packages/livechat/src/routes/Chat/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ class ChatContainer extends Component {
return user;
}

const {
iframe: { defaultDepartment },
} = store.state;

if (!guest?.department && defaultDepartment) {
guest.department = defaultDepartment;
}

const visitor = { token, ...guest };
const { visitor: newUser } = await Livechat.grantVisitor({ visitor });
await dispatch({ user: newUser });
Expand Down
Loading

0 comments on commit dbc79b7

Please sign in to comment.