Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Return departmentsAllowedToForward property in livechat/config endpoint #33060

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f82b8e9
feat: Return departmentsAllowedToForward property in livechat/config …
matheusbsilva137 Aug 14, 2024
2d12470
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Aug 15, 2024
0754609
improve return type
matheusbsilva137 Aug 23, 2024
f889485
add end-to-end tests
matheusbsilva137 Aug 23, 2024
073d0db
Merge branch 'develop' into feat/livechat-config-departments-forward
matheusbsilva137 Aug 23, 2024
025d3bd
Create changeset
matheusbsilva137 Aug 23, 2024
c8d3ee1
only run new test in EE
matheusbsilva137 Aug 23, 2024
0d47922
Improve typing again
matheusbsilva137 Aug 23, 2024
531911a
Merge branch 'develop' into feat/livechat-config-departments-forward
MarcosSpessatto Aug 28, 2024
aa11179
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Aug 28, 2024
0b38906
Merge branch 'develop' into feat/livechat-config-departments-forward
MarcosSpessatto Aug 29, 2024
7980ee9
use extends Document instead of Partial
matheusbsilva137 Aug 30, 2024
4eeba61
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Oct 8, 2024
b9d35c9
Use more before and after in tests
matheusbsilva137 Oct 8, 2024
ec623c3
Merge branch 'develop' into feat/livechat-config-departments-forward
matheusbsilva137 Oct 9, 2024
449c39e
Merge branch 'develop' into feat/livechat-config-departments-forward
MarcosSpessatto Oct 9, 2024
bbb871e
Merge branch 'develop' into feat/livechat-config-departments-forward
MarcosSpessatto Oct 9, 2024
cf7476c
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 19, 2024
2867204
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 20, 2024
ae17fe0
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 20, 2024
1f7f944
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 23, 2024
a251d44
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 23, 2024
9d77d84
Merge branch 'develop' into feat/livechat-config-departments-forward
tapiarafael Dec 23, 2024
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
7 changes: 7 additions & 0 deletions .changeset/beige-kiwis-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/livechat": minor
"@rocket.chat/model-typings": minor
---

Added `departmentsAllowedToForward` property to departments returned in the `livechat/config` endpoint
26 changes: 11 additions & 15 deletions apps/meteor/app/livechat/server/api/lib/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,19 @@ async function findTriggers(): Promise<Pick<ILivechatTrigger, '_id' | 'actions'

async function findDepartments(
businessUnit?: string,
): Promise<Pick<ILivechatDepartment, '_id' | 'name' | 'showOnRegistration' | 'showOnOfflineForm'>[]> {
): Promise<Pick<ILivechatDepartment, '_id' | 'name' | 'showOnRegistration' | 'showOnOfflineForm' | 'departmentsAllowedToForward'>[]> {
// TODO: check this function usage
return (
await (
await LivechatDepartment.findEnabledWithAgentsAndBusinessUnit(businessUnit, {
_id: 1,
name: 1,
showOnRegistration: 1,
showOnOfflineForm: 1,
})
).toArray()
).map(({ _id, name, showOnRegistration, showOnOfflineForm }) => ({
_id,
name,
showOnRegistration,
showOnOfflineForm,
}));
await LivechatDepartment.findEnabledWithAgentsAndBusinessUnit<
Pick<ILivechatDepartment, '_id' | 'name' | 'showOnRegistration' | 'showOnOfflineForm' | 'departmentsAllowedToForward'>
>(businessUnit, {
_id: 1,
name: 1,
showOnRegistration: 1,
showOnOfflineForm: 1,
departmentsAllowedToForward: 1,
})
).toArray();
}

export function findGuest(token: string): Promise<ILivechatVisitor | null> {
Expand Down
16 changes: 8 additions & 8 deletions apps/meteor/ee/server/models/raw/LivechatDepartment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ declare module '@rocket.chat/model-typings' {
): Promise<UpdateResult>;
unfilteredRemove(query: Filter<ILivechatDepartment>): Promise<DeleteResult>;
removeParentAndAncestorById(id: string): Promise<UpdateResult | Document>;
findEnabledWithAgentsAndBusinessUnit(
findEnabledWithAgentsAndBusinessUnit<T extends Document = ILivechatDepartment>(
businessUnit: string,
projection: FindOptions<ILivechatDepartment>['projection'],
): Promise<FindCursor<ILivechatDepartment>>;
projection: FindOptions<T>['projection'],
): Promise<FindCursor<T>>;
findByParentId(parentId: string, options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>;
findAgentsByBusinessHourId(businessHourId: string): AggregationCursor<{ agentIds: string[] }>;
}
Expand Down Expand Up @@ -74,19 +74,19 @@ export class LivechatDepartmentEE extends LivechatDepartmentRaw implements ILive
return this.updateMany({ parentId: id }, { $unset: { parentId: 1 }, $pull: { ancestors: id } });
}

async findEnabledWithAgentsAndBusinessUnit(
async findEnabledWithAgentsAndBusinessUnit<T extends Document = ILivechatDepartment>(
businessUnit: string,
projection: FindOptions<ILivechatDepartment>['projection'],
): Promise<FindCursor<ILivechatDepartment>> {
projection: FindOptions<T>['projection'],
): Promise<FindCursor<T>> {
if (!businessUnit) {
return super.findEnabledWithAgents(projection);
return super.findEnabledWithAgents<T>(projection);
}
const unit = await LivechatUnit.findOneById(businessUnit, { projection: { _id: 1 } });
if (!unit) {
throw new Meteor.Error('error-unit-not-found', `Error! No Active Business Unit found with id: ${businessUnit}`);
}

return super.findActiveByUnitIds([businessUnit], { projection });
return super.findActiveByUnitIds<T>([businessUnit], { projection });
}

findByParentId(parentId: string, options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment> {
Expand Down
16 changes: 8 additions & 8 deletions apps/meteor/server/models/raw/LivechatDepartment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,23 +291,23 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen
return this.updateMany({ _id: { $in: _ids } }, { $inc: { numAgents: -1 } });
}

findEnabledWithAgents(projection: FindOptions<ILivechatDepartment>['projection'] = {}): FindCursor<ILivechatDepartment> {
findEnabledWithAgents<T extends Document = ILivechatDepartment>(projection: FindOptions<T>['projection'] = {}): FindCursor<T> {
const query = {
numAgents: { $gt: 0 },
enabled: true,
};
return this.find(query, projection && { projection });
return this.find<T>(query, projection && { projection });
}

async findEnabledWithAgentsAndBusinessUnit(
async findEnabledWithAgentsAndBusinessUnit<T extends Document = ILivechatDepartment>(
_: any,
projection: FindOptions<ILivechatDepartment>['projection'] = {},
): Promise<FindCursor<ILivechatDepartment>> {
projection: FindOptions<T>['projection'] = {},
): Promise<FindCursor<T>> {
const query = {
numAgents: { $gt: 0 },
enabled: true,
};
return this.find(query, projection && { projection });
return this.find<T>(query, projection && { projection });
}

findOneByIdOrName(_idOrName: string, options: FindOptions<ILivechatDepartment> = {}): Promise<ILivechatDepartment | null> {
Expand Down Expand Up @@ -340,7 +340,7 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen
return this.countDocuments({ parentId: unitId });
}

findActiveByUnitIds(unitIds: string[], options: FindOptions<ILivechatDepartment> = {}): FindCursor<ILivechatDepartment> {
findActiveByUnitIds<T extends Document = ILivechatDepartment>(unitIds: string[], options: FindOptions<T> = {}): FindCursor<T> {
const query = {
enabled: true,
numAgents: { $gt: 0 },
Expand All @@ -350,7 +350,7 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen
},
};

return this.find(query, options);
return this.find<T>(query, options);
}

findNotArchived(options: FindOptions<ILivechatDepartment> = {}): FindCursor<ILivechatDepartment> {
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/tests/data/livechat/department.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const createDepartmentWithMethod = ({
initialAgents = [],
allowReceiveForwardOffline = false,
fallbackForwardDepartment,
departmentsAllowedToForward,
name,
departmentUnit,
userCredentials = credentials,
Expand All @@ -54,6 +55,7 @@ export const createDepartmentWithMethod = ({
initialAgents?: { agentId: string; username: string }[];
allowReceiveForwardOffline?: boolean;
fallbackForwardDepartment?: string;
departmentsAllowedToForward?: string[];
name?: string;
departmentUnit?: { _id?: string };
userCredentials?: Credentials;
Expand All @@ -77,6 +79,7 @@ export const createDepartmentWithMethod = ({
description: 'created from api',
allowReceiveForwardOffline,
fallbackForwardDepartment,
departmentsAllowedToForward,
},
initialAgents,
departmentUnit,
Expand Down Expand Up @@ -149,9 +152,11 @@ export const addOrRemoveAgentFromDepartment = async (
export const createDepartmentWithAnOfflineAgent = async ({
allowReceiveForwardOffline = false,
fallbackForwardDepartment,
departmentsAllowedToForward,
}: {
allowReceiveForwardOffline?: boolean;
fallbackForwardDepartment?: string;
departmentsAllowedToForward?: string[];
}): Promise<{
department: ILivechatDepartment;
agent: {
Expand All @@ -164,6 +169,7 @@ export const createDepartmentWithAnOfflineAgent = async ({
const department = (await createDepartmentWithMethod({
allowReceiveForwardOffline,
fallbackForwardDepartment,
departmentsAllowedToForward,
})) as ILivechatDepartment;

await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
Expand Down
72 changes: 71 additions & 1 deletion apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';

import { sleep } from '../../../../lib/utils/sleep';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
import { addOrRemoveAgentFromDepartment, createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
import {
addOrRemoveAgentFromDepartment,
createDepartmentWithAnOfflineAgent,
createDepartmentWithAnOnlineAgent,
deleteDepartment,
} from '../../../data/livechat/department';
import {
createVisitor,
createLivechatRoom,
makeAgentUnavailable,
closeOmnichannelRoom,
sendMessage,
deleteVisitor,
createDepartment,
} from '../../../data/livechat/rooms';
import { createBotAgent, getRandomVisitorToken } from '../../../data/livechat/users';
import type { WithRequiredProperty } from '../../../data/livechat/utils';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { deleteUser } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';

describe('LIVECHAT - Utils', () => {
Expand Down Expand Up @@ -71,6 +81,38 @@ describe('LIVECHAT - Utils', () => {
});

describe('livechat/config', () => {
let emptyDepartment: ILivechatDepartment;
let forwardDepartment: ILivechatDepartment;
let testDepartment: ILivechatDepartment;
let agent: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };
let agent2: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };

before(async () => {
if (!IS_EE) {
return;
}

emptyDepartment = await createDepartment();
({ department: forwardDepartment, agent } = await createDepartmentWithAnOnlineAgent());
({ department: testDepartment, agent: agent2 } = await createDepartmentWithAnOfflineAgent({
departmentsAllowedToForward: [forwardDepartment._id],
}));
});

after(() => {
if (!IS_EE) {
return;
}

return Promise.all([
deleteDepartment(emptyDepartment._id),
deleteDepartment(forwardDepartment._id),
deleteDepartment(testDepartment._id),
deleteUser(agent.user),
deleteUser(agent2.user),
]);
});

it('should return enabled: false if livechat is disabled', async () => {
await updateSetting('Livechat_enabled', false);
const { body } = await request.get(api('livechat/config')).set(credentials);
Expand Down Expand Up @@ -171,6 +213,34 @@ describe('LIVECHAT - Utils', () => {
expect(body.config).to.have.property('room');
expect(body.config.room).to.have.property('_id', newRoom._id);
});
(IS_EE ? it : it.skip)('should return list of departments with at least one agent', async () => {
const { body } = await request.get(api('livechat/config')).set(credentials);

expect(body).to.have.property('success', true);
expect(body).to.have.property('config');
expect(body.config).to.have.property('departments');
expect(body.config.departments).to.be.an('array').with.lengthOf.at.least(2);

expect(body.config.departments).to.not.deep.include({
_id: emptyDepartment._id,
name: emptyDepartment.name,
showOnRegistration: emptyDepartment.showOnRegistration,
showOnOfflineForm: emptyDepartment.showOnOfflineForm,
});
expect(body.config.departments).to.deep.include({
_id: forwardDepartment._id,
name: forwardDepartment.name,
showOnRegistration: forwardDepartment.showOnRegistration,
showOnOfflineForm: forwardDepartment.showOnOfflineForm,
});
expect(body.config.departments).to.deep.include({
_id: testDepartment._id,
name: testDepartment.name,
showOnRegistration: testDepartment.showOnRegistration,
showOnOfflineForm: testDepartment.showOnOfflineForm,
departmentsAllowedToForward: [forwardDepartment._id],
});
});
});

describe('livechat/page.visited', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/livechat/src/definitions/departments.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export type Department = {
_id: string;
name: string;
showOnRegistration?: boolean;
showOnOfflineForm?: boolean;
departmentsAllowedToForward?: string[];
[key: string]: unknown;
};
10 changes: 6 additions & 4 deletions packages/model-typings/src/models/ILivechatDepartmentModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export interface ILivechatDepartmentModel extends IBaseModel<ILivechatDepartment
updateById(_id: string, update: Partial<ILivechatDepartment>): Promise<Document | UpdateResult>;
updateNumAgentsById(_id: string, numAgents: number): Promise<Document | UpdateResult>;
decreaseNumberOfAgentsByIds(_ids: string[]): Promise<Document | UpdateResult>;
findEnabledWithAgents(projection?: FindOptions<ILivechatDepartment>['projection']): FindCursor<ILivechatDepartment>;
findEnabledWithAgentsAndBusinessUnit(
findEnabledWithAgents<T extends Document = ILivechatDepartment>(
projection?: FindOptions<ILivechatDepartment>['projection'],
): FindCursor<T>;
findEnabledWithAgentsAndBusinessUnit<T extends Document = ILivechatDepartment>(
_: any,
projection: FindOptions<ILivechatDepartment>['projection'],
): Promise<FindCursor<ILivechatDepartment>>;
projection: FindOptions<T>['projection'],
): Promise<FindCursor<T>>;
findOneByIdOrName(_idOrName: string, options?: FindOptions<ILivechatDepartment>): Promise<ILivechatDepartment | null>;
findByUnitIds(unitIds: string[], options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>;
countDepartmentsInUnit(unitId: string): Promise<number>;
Expand Down
Loading