Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-laws-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes a fetch infinite loop when adding agents to a department
6 changes: 4 additions & 2 deletions apps/meteor/client/components/AutoCompleteAgent.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { PaginatedSelectFiltered } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import type { ReactElement } from 'react';
import type { AriaAttributes, ReactElement } from 'react';
import { memo, useMemo, useState } from 'react';

import { useRecordList } from '../hooks/lists/useRecordList';
import { AsyncStatePhase } from '../lib/asyncState';
import { useAgentsList } from './Omnichannel/hooks/useAgentsList';

type AutoCompleteAgentProps = {
type AutoCompleteAgentProps = Pick<AriaAttributes, 'aria-labelledby'> & {
value: string;
error?: string;
placeholder?: string;
Expand All @@ -31,6 +31,7 @@ const AutoCompleteAgent = ({
onlyAvailable = false,
withTitle = false,
onChange,
'aria-labelledby': ariaLabelledBy,
}: AutoCompleteAgentProps): ReactElement => {
const [agentsFilter, setAgentsFilter] = useState<string>('');

Expand All @@ -57,6 +58,7 @@ const AutoCompleteAgent = ({
setFilter={setAgentsFilter as (value: string | number | undefined) => void}
options={agentsItems}
data-qa='autocomplete-agent'
aria-labelledby={ariaLabelledBy}
endReached={
agentsPhase === AsyncStatePhase.LOADING ? (): void => undefined : (start): void => loadMoreAgents(start, Math.min(50, agentsTotal))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const useAgentsList = (

return {
items,
itemCount: total + 1,
itemCount: total,
};
},
[excludeId, getAgents, haveAll, haveNoAgentsSelectedOption, onlyAvailable, showIdleAgents, t, text],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Box, Button } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { AriaAttributes } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';

import AutoCompleteAgent from '../../../../components/AutoCompleteAgent';
import { useEndpointAction } from '../../../../hooks/useEndpointAction';
import type { IDepartmentAgent } from '../definitions';

function AddAgent({ agentList, onAdd }: { agentList: IDepartmentAgent[]; onAdd: (agent: IDepartmentAgent) => void }) {
type AddAgentProps = Pick<AriaAttributes, 'aria-labelledby'> & {
agentList: IDepartmentAgent[];
onAdd: (agent: IDepartmentAgent) => void;
};

function AddAgent({ agentList, onAdd, 'aria-labelledby': ariaLabelledBy }: AddAgentProps) {
const { t } = useTranslation();

const [userId, setUserId] = useState('');
Expand Down Expand Up @@ -37,7 +43,7 @@ function AddAgent({ agentList, onAdd }: { agentList: IDepartmentAgent[]; onAdd:
});

return (
<Box display='flex' alignItems='center'>
<Box role='group' aria-labelledby={ariaLabelledBy} display='flex' alignItems='center'>
<AutoCompleteAgent value={userId} onChange={handleAgent} />
<Button disabled={!userId} onClick={handleSave} mis={8} primary>
{t('Add')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Pagination } from '@rocket.chat/fuselage';
import type { AriaAttributes } from 'react';
import { useMemo } from 'react';
import type { Control, UseFormRegister } from 'react-hook-form';
import { useWatch, useFieldArray } from 'react-hook-form';
Expand All @@ -10,12 +11,12 @@ import type { EditDepartmentFormData } from '../definitions';
import AddAgent from './AddAgent';
import AgentRow from './AgentRow';

type DepartmentAgentsTableProps = {
type DepartmentAgentsTableProps = Pick<AriaAttributes, 'aria-labelledby'> & {
control: Control<EditDepartmentFormData>;
register: UseFormRegister<EditDepartmentFormData>;
};

function DepartmentAgentsTable({ control, register }: DepartmentAgentsTableProps) {
function DepartmentAgentsTable({ control, register, 'aria-labelledby': ariaLabelledBy }: DepartmentAgentsTableProps) {
const { t } = useTranslation();
const { fields, append, remove } = useFieldArray({ control, name: 'agentList' });
const agentList = useWatch({ control, name: 'agentList' });
Expand All @@ -25,7 +26,7 @@ function DepartmentAgentsTable({ control, register }: DepartmentAgentsTableProps

return (
<>
<AddAgent agentList={agentList} data-qa='DepartmentSelect-AgentsTable' onAdd={append} />
<AddAgent aria-labelledby={ariaLabelledBy} agentList={agentList} data-qa='DepartmentSelect-AgentsTable' onAdd={append} />

<GenericTable>
<GenericTableHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen
const chatClosingTagsField = useId();
const allowReceiveForwardOffline = useId();
const unitFieldId = useId();
const agentsLabelId = useId();

return (
<Page flexDirection='row'>
Expand Down Expand Up @@ -444,9 +445,11 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen
<Divider mb={16} />

<Field>
<FieldLabel mb={4}>{t('Agents')}</FieldLabel>
<FieldLabel id={agentsLabelId} mb={4}>
{t('Agents')}
</FieldLabel>
<Box display='flex' flexDirection='column' height='50vh'>
<DepartmentsAgentsTable control={control} register={register} />
<DepartmentsAgentsTable aria-labelledby={agentsLabelId} control={control} register={register} />
</Box>
</Field>
</FieldGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Page } from '@playwright/test';
import { IS_EE } from '../config/constants';
import { Users } from '../fixtures/userStates';
import { OmnichannelDepartments } from '../page-objects';
import { createAgent } from '../utils/omnichannel/agents';
import { createDepartment, deleteDepartment } from '../utils/omnichannel/departments';
import { test, expect } from '../utils/test';

Expand All @@ -19,15 +20,18 @@ test.describe('OC - Manage Departments', () => {
test.skip(!IS_EE, 'Enterprise Edition Only');

let poOmnichannelDepartments: OmnichannelDepartments;
let agent: Awaited<ReturnType<typeof createAgent>>;

test.beforeAll(async ({ api }) => {
// turn on department removal
await api.post('/settings/Omnichannel_enable_department_removal', { value: true });
agent = await createAgent(api, 'user1');
});

test.afterAll(async ({ api }) => {
// turn off department removal
await api.post('/settings/Omnichannel_enable_department_removal', { value: false });
await agent.delete();
});

test.describe('Create first department', async () => {
Expand All @@ -38,7 +42,7 @@ test.describe('OC - Manage Departments', () => {
await poOmnichannelDepartments.sidenav.linkDepartments.click();
});

test('Create department', async () => {
test('Create department', async ({ page }) => {
const departmentName = faker.string.uuid();

await poOmnichannelDepartments.headingButtonNew('Create department').click();
Expand All @@ -65,12 +69,40 @@ test.describe('OC - Manage Departments', () => {
await expect(poOmnichannelDepartments.errorMessage(ERROR.requiredEmail)).not.toBeVisible();
});

await test.step('expect create new department', async () => {
await test.step('expect to fill required fields', async () => {
await poOmnichannelDepartments.btnEnabled.click();
await poOmnichannelDepartments.inputName.fill(departmentName);
await poOmnichannelDepartments.inputEmail.fill(faker.internet.email());
await poOmnichannelDepartments.btnSave.click();
});

await test.step('expect to fetch agents a reasonable number of times', async () => {
let requestCount = 0;

await page.route('**/v1/livechat/users/agent*', async (route) => {
requestCount++;
await route.continue();
});

await poOmnichannelDepartments.inputAgents.click();
await poOmnichannelDepartments.inputAgents.fill('user1');

await page.waitForTimeout(1000);
await expect(requestCount).toBeGreaterThan(0);
await expect(requestCount).toBeLessThan(3);

await poOmnichannelDepartments.inputAgents.click();
});

await test.step('expect to add an agent', async () => {
await poOmnichannelDepartments.inputAgents.click();
await poOmnichannelDepartments.inputAgents.fill('user1');
await poOmnichannelDepartments.findOption('user1 (@user1)').click();
await poOmnichannelDepartments.btnAddAgent.click();
await expect(poOmnichannelDepartments.findAgentRow('user1')).toBeVisible();
});

await test.step('expect create new department', async () => {
await poOmnichannelDepartments.btnSave.click();
await poOmnichannelDepartments.search(departmentName);
await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible();
});
Expand Down
16 changes: 16 additions & 0 deletions apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,20 @@ export class OmnichannelDepartments {
await this.inputUnit.click();
await this.findOption(unitName).click();
}

get fieldGroupAgents() {
return this.page.getByLabel('Agents', { exact: true });
}

get inputAgents() {
return this.fieldGroupAgents.getByRole('textbox');
}

get btnAddAgent() {
return this.fieldGroupAgents.getByRole('button', { name: 'Add', exact: true });
}

findAgentRow(name: string) {
return this.page.locator('tr', { has: this.page.getByText(name, { exact: true }) });
}
}
Loading