Skip to content

Commit

Permalink
Merge branch 'develop' into issue_29516_Files_menu_ignores_new_server…
Browse files Browse the repository at this point in the history
…_address
  • Loading branch information
Rottenblasters committed Jul 2, 2023
2 parents 33c73b4 + baaa38f commit 668057d
Show file tree
Hide file tree
Showing 89 changed files with 1,280 additions and 1,674 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-rockets-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

regression: `onLogin` hook not destructuring user prop
5 changes: 5 additions & 0 deletions .changeset/olive-pears-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/ui-client': patch
---

Fixed required custom fields considering blank spaces as valid.
6 changes: 6 additions & 0 deletions .changeset/quick-coats-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/model-typings": patch
---

fix: newly added agent not following business hours
5 changes: 5 additions & 0 deletions .changeset/spicy-kiwis-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

fix: Omnichannel queue not running for all queues
5 changes: 5 additions & 0 deletions .changeset/thin-hats-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Avoid updating a user's livechat status on login when its status is set to offline
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:
rc-docker-tag-alpine: '${{ steps.docker.outputs.gh-docker-tag }}.alpine'
node-version: ${{ steps.var.outputs.node-version }}
# this is 100% intentional, secrets are not available for forks, so ee-tests will always fail
# to avoid this, we are using a dummy license, expiring at 2023-06-30
enterprise-license: Z2Dg0RC3kyxjuklSE6qfqyvD2xSD+oTYcS9OesJG0523r7rSPjv59LTQqPcp5E61qQYM3MOKoW3mDrurw4h78nVbsfrF2DoJZeNjRFQfIbgwcdPwtmnqPpDvAslszHY16VzM7O7EYqAqp/9mlnRzs1iJY+W3w1r6HWBlVMb9u41bl5HBSpX6Nxw8YxL4mizwOpjxewQbPQvNTLJNAW6w0nCzF5A3CKBhD9fziadedVMLOuXBuR8kIl8zbIAfqpHmL8SvakvQAbZEjWWQshmH+C9CKA5PppkmA8Q1DNWQoVtHSiYDK8RRjAEx+0oGflklzFyhJFDvD+ohZduNtNCgrJmxP5VFrVrLSK4BXgTSwwnaSKa2N+Qx0CmuRfu7nCPc1Cf6h6+k2TXvzkE4Z0ZJnDV1khu611glAr99bHdwF+bMX3XZI66bS8KqnHEukCt5xei25iKJ2xrfmGuiAkAuKHKzBmTEmXM0pGhkfDhA9jhxG3Atoj1A5y8vdrs88voF+UuNFZ6k9sKtdvrWIWClnkatPE+41ggbzCsOhFz07BvRWaEtw2Kenipl4Vtag4qmFpUaUfsuouH99M3gDlysDZO3x5aH8yfzvFeL5WDMvsmdEHNLpHl89WsPCONvx0JjRSdwcCA1NrRuVy1Ncu0S0bRByn7HZqoY9u6HPkXKBxQ=
# to avoid this, we are using a dummy license, expiring at 2024-06-30
enterprise-license: WMa5i+/t/LZbYOj8u3XUkivRhWBtWO6ycUjaZoVAw2DxMfdyBIAa2gMMI4x7Z2BrTZIZhFEImfOxcXcgD0QbXHGBJaMI+eYG+eofnVWi2VA7RWbpvWTULgPFgyJ4UEFeCOzVjcBLTQbmMSam3u0RlekWJkfAO0KnmLtsaEYNNA2rz1U+CLI/CdNGfdqrBu5PZZbGkH0KEzyIZMaykOjzvX+C6vd7fRxh23HecwhkBbqE8eQsCBt2ad0qC4MoVXsDaSOmSzGW+aXjuXt/9zjvrLlsmWQTSlkrEHdNkdywm0UkGxqz3+CP99n0WggUBioUiChjMuNMoceWvDvmxYP9Ml2NpYU7SnfhjmMFyXOah8ofzv8w509Y7XODvQBz+iB4Co9YnF3vT96HDDQyAV5t4jATE+0t37EAXmwjTi3qqyP7DLGK/revl+mlcwJ5kS4zZBsm1E4519FkXQOZSyWRnPdjqvh4mCLqoispZ49wKvklDvjPxCSP9us6cVXLDg7NTJr/4pfxLPOkvv7qCgugDvlDx17bXpQFPSDxmpw66FLzvb5Id0dkWjOzrRYSXb0bFWoUQjtHFzmcpFkyVhOKrQ9zA9+Zm7vXmU9Y2l2dK79EloOuHMSYAqsPEag8GMW6vI/cT4iIjHGGDePKnD0HblvTEKzql11cfT/abf2IiaY=
steps:
- uses: Bhacaz/checkout-files@v2
with:
Expand Down
7 changes: 5 additions & 2 deletions apps/meteor/app/lib/server/functions/saveUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,14 @@ export const saveUser = async function (userId, userData) {

await Users.updateOne({ _id: userData._id }, updateUser);

await callbacks.run('afterSaveUser', userData);

// App IPostUserUpdated event hook
const userUpdated = await Users.findOneById(userId);

await callbacks.run('afterSaveUser', {
user: userUpdated,
oldUser: oldUserData,
});

await Apps.triggerEvent(AppEvents.IPostUserUpdated, {
user: userUpdated,
previousUser: oldUserData,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import moment from 'moment-timezone';
import { ILivechatAgentStatus } from '@rocket.chat/core-typings';
import type { ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings';
import type { ILivechatBusinessHoursModel, IUsersModel } from '@rocket.chat/model-typings';
import { LivechatBusinessHours, Users } from '@rocket.chat/models';
import type { UpdateFilter } from 'mongodb';

import type { IWorkHoursCronJobsWrapper } from '../../../../server/models/raw/LivechatBusinessHours';
import { businessHourLogger } from '../lib/logger';
import { filterBusinessHoursThatMustBeOpened } from './Helper';

export interface IBusinessHourBehavior {
findHoursToCreateJobs(): Promise<IWorkHoursCronJobsWrapper[]>;
openBusinessHoursByDayAndHour(day: string, hour: string): Promise<void>;
closeBusinessHoursByDayAndHour(day: string, hour: string): Promise<void>;
onDisableBusinessHours(): Promise<void>;
onAddAgentToDepartment(options?: Record<string, any>): Promise<any>;
onAddAgentToDepartment(options?: { departmentId: string; agentsId: string[] }): Promise<any>;
onRemoveAgentFromDepartment(options?: Record<string, any>): Promise<any>;
onRemoveDepartment(department?: ILivechatDepartment): Promise<any>;
onStartBusinessHours(): Promise<void>;
afterSaveBusinessHours(businessHourData: ILivechatBusinessHour): Promise<void>;
allowAgentChangeServiceStatus(agentId: string): Promise<boolean>;
changeAgentActiveStatus(agentId: string, status: string): Promise<any>;
// If a new agent is created, this callback will be called
onNewAgentCreated(agentId: string): Promise<void>;
}

export interface IBusinessHourType {
Expand All @@ -44,14 +49,39 @@ export abstract class AbstractBusinessHourBehavior {
return this.UsersRepository.isAgentWithinBusinessHours(agentId);
}

async changeAgentActiveStatus(agentId: string, status: string): Promise<any> {
async changeAgentActiveStatus(agentId: string, status: ILivechatAgentStatus): Promise<any> {
return this.UsersRepository.setLivechatStatusIf(
agentId,
status,
{ livechatStatusSystemModified: true },
// Why this works: statusDefault is the property set when a user manually changes their status
// So if it's set to offline, we can be sure the user will be offline after login and we can skip the update
{ livechatStatusSystemModified: true, statusDefault: { $ne: 'offline' } },
{ livechatStatusSystemModified: true },
);
}

async onNewAgentCreated(agentId: string): Promise<void> {
businessHourLogger.debug(`Executing onNewAgentCreated for agentId: ${agentId}`);

const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour();
if (!defaultBusinessHour) {
businessHourLogger.debug(`No default business hour found for agentId: ${agentId}`);
return;
}

const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]);
if (!businessHourToOpen.length) {
businessHourLogger.debug(
`No business hour to open found for agentId: ${agentId}. Default business hour is closed. Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.NOT_AVAILABLE}`,
);
await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE);
return;
}

await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id);

businessHourLogger.debug(`Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.AVAILABLE}`);
}
}

export abstract class AbstractBusinessHourType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@ import { Users } from '@rocket.chat/models';
import type { IBusinessHourBehavior, IBusinessHourType } from './AbstractBusinessHour';
import { settings } from '../../../settings/server';
import { callbacks } from '../../../../lib/callbacks';

const cronJobDayDict: Record<string, number> = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
};
import { businessHourLogger } from '../lib/logger';

export class BusinessHourManager {
private types: Map<string, IBusinessHourType> = new Map();
Expand All @@ -35,6 +26,7 @@ export class BusinessHourManager {

async startManager(): Promise<void> {
await this.createCronJobsForWorkHours();
businessHourLogger.debug('Cron jobs created, setting up callbacks');
this.setupCallbacks();
await this.behavior.onStartBusinessHours();
}
Expand Down Expand Up @@ -115,12 +107,19 @@ export class BusinessHourManager {
callbacks.priority.HIGH,
'business-hour-livechat-on-save-agent-department',
);
callbacks.add(
'livechat.onNewAgentCreated',
this.behavior.onNewAgentCreated.bind(this),
callbacks.priority.HIGH,
'business-hour-livechat-on-agent-created',
);
}

private removeCallbacks(): void {
callbacks.remove('livechat.removeAgentDepartment', 'business-hour-livechat-on-remove-agent-department');
callbacks.remove('livechat.afterRemoveDepartment', 'business-hour-livechat-after-remove-department');
callbacks.remove('livechat.saveAgentDepartment', 'business-hour-livechat-on-save-agent-department');
callbacks.remove('livechat.onNewAgentCreated', 'business-hour-livechat-on-agent-created');
}

private async createCronJobsForWorkHours(): Promise<void> {
Expand All @@ -137,12 +136,17 @@ export class BusinessHourManager {
await Promise.all(finish.map(({ day, times }) => this.scheduleCronJob(times, day, 'close', this.closeWorkHoursCallback)));
}

private async scheduleCronJob(items: string[], day: string, type: string, job: (day: string, hour: string) => void): Promise<void> {
private async scheduleCronJob(
items: string[],
day: string,
type: 'open' | 'close',
job: (day: string, hour: string) => void,
): Promise<void> {
await Promise.all(
items.map((hour) => {
const jobName = `${day}/${hour}/${type}`;
const time = moment(hour, 'HH:mm');
const scheduleAt = `${time.minutes()} ${time.hours()} * * ${cronJobDayDict[day]}`;
const time = moment(hour, 'HH:mm').day(day);
const jobName = `${time.format('dddd')}/${time.format('HH:mm')}/${type}`;
const scheduleAt = `${time.minutes()} ${time.hours()} * * ${time.day()}`;
this.addToCache(jobName);
return this.cronJobs.add(jobName, scheduleAt, () => job(day, hour));
}),
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/app/livechat/server/business-hour/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { LivechatBusinessHours, Users } from '@rocket.chat/models';

import { createDefaultBusinessHourRow } from './LivechatBusinessHours';
import { businessHourLogger } from '../lib/logger';

export const filterBusinessHoursThatMustBeOpened = async (
businessHours: ILivechatBusinessHour[],
Expand Down Expand Up @@ -52,8 +53,10 @@ export const openBusinessHourDefault = async (): Promise<void> => {
},
});
const businessHoursToOpenIds = (await filterBusinessHoursThatMustBeOpened(activeBusinessHours)).map((businessHour) => businessHour._id);
businessHourLogger.debug({ msg: 'Opening default business hours', businessHoursToOpenIds });
await Users.openAgentsBusinessHoursByBusinessHourId(businessHoursToOpenIds);
await Users.updateLivechatStatusBasedOnBusinessHours();
businessHourLogger.debug('Done opening default business hours');
};

export const createDefaultBusinessHourIfNotExists = async (): Promise<void> => {
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/livechat/server/business-hour/Single.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import type { IBusinessHourBehavior } from './AbstractBusinessHour';
import { AbstractBusinessHourBehavior } from './AbstractBusinessHour';
import { openBusinessHourDefault } from './Helper';
import { businessHourLogger } from '../lib/logger';

export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior {
async openBusinessHoursByDayAndHour(day: string, hour: string): Promise<void> {
Expand All @@ -25,6 +26,7 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp
}

async onStartBusinessHours(): Promise<void> {
businessHourLogger.debug('Starting Single Business Hours');
return openBusinessHourDefault();
}

Expand Down
10 changes: 5 additions & 5 deletions apps/meteor/app/livechat/server/business-hour/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { cronJobs } from '@rocket.chat/cron';
import type { IUser } from '@rocket.chat/core-typings';
import { Accounts } from 'meteor/accounts-base';

import { BusinessHourManager } from './BusinessHourManager';
import { SingleBusinessHourBehavior } from './Single';
Expand All @@ -16,8 +17,7 @@ Meteor.startup(async () => {
businessHourManager.registerBusinessHourBehavior(new BusinessHourBehaviorClass());
businessHourManager.registerBusinessHourType(new DefaultBusinessHour());

Accounts.onLogin(
async ({ user }: { user: any }) =>
user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && businessHourManager.onLogin(user._id),
);
Accounts.onLogin(({ user }: { user: IUser }) => {
void (user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && businessHourManager.onLogin(user._id));
});
});
50 changes: 39 additions & 11 deletions apps/meteor/app/livechat/server/hooks/afterUserActions.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import type { UsersUpdateParamsPOST } from '@rocket.chat/rest-typings';

import { callbacks } from '../../../../lib/callbacks';
import { Livechat } from '../lib/Livechat';
import { callbackLogger } from '../lib/logger';

type UserData = UsersUpdateParamsPOST['data'] & { _id: string };
type IAfterSaveUserProps = {
user: IUser;
oldUser: IUser | null;
};

const wasAgent = (user: Pick<IUser, 'roles'> | null) => user?.roles?.includes('livechat-agent');
const isAgent = (user: Pick<IUser, 'roles'> | null) => user?.roles?.includes('livechat-agent');

const handleAgentUpdated = async (userData: IAfterSaveUserProps) => {
const {
user: { _id: userId, username },
user: newUser,
oldUser,
} = userData;

if (wasAgent(oldUser) && !isAgent(newUser)) {
callbackLogger.debug('Removing agent', userId);
await Livechat.removeAgent(username);
}

const handleAgentUpdated = async (userData: UserData) => {
if (!userData?.roles?.includes('livechat-agent')) {
await Users.unsetExtension(userData._id);
if (!wasAgent(oldUser) && isAgent(newUser)) {
callbackLogger.debug('Adding agent', userId);
await Livechat.addAgent(username);
}
};

const handleDeactivateUser = async (userData: IUser) => {
if (userData?.roles?.includes('livechat-agent')) {
await Users.unsetExtension(userData._id);
const handleDeactivateUser = async (user: IUser) => {
if (wasAgent(user)) {
callbackLogger.debug('Removing agent', user._id);
await Livechat.removeAgent(user.username);
}
};

callbacks.add('afterSaveUser', handleAgentUpdated, callbacks.priority.LOW, 'livechat-after-save-user-remove-extension');
const handleActivateUser = async (user: IUser) => {
if (isAgent(user)) {
callbackLogger.debug('Adding agent', user._id);
await Livechat.addAgent(user.username);
}
};

callbacks.add('afterSaveUser', handleAgentUpdated, callbacks.priority.LOW, 'livechat-after-save-user-update-agent');

callbacks.add('afterDeactivateUser', handleDeactivateUser, callbacks.priority.LOW, 'livechat-after-deactivate-user-remove-agent');

callbacks.add('afterDeactivateUser', handleDeactivateUser, callbacks.priority.LOW, 'livechat-after-deactivate-user-remove-extension');
callbacks.add('afterActivateUser', handleActivateUser, callbacks.priority.LOW, 'livechat-after-activate-user-add-agent');
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LivechatRooms } from '@rocket.chat/models';

import { callbacks } from '../../../../lib/callbacks';
import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload';
import { callbackLogger } from '../lib/callbackLogger';
import { callbackLogger } from '../lib/logger';

callbacks.add(
'afterSaveMessage',
Expand Down
9 changes: 4 additions & 5 deletions apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,9 @@ export const Livechat = {
if (await addUserRolesAsync(user._id, ['livechat-agent'])) {
await Users.setOperator(user._id, true);
await this.setUserStatusLivechat(user._id, user.status !== 'offline' ? 'available' : 'not-available');

callbacks.runAsync('livechat.onNewAgentCreated', user._id);

return user;
}

Expand Down Expand Up @@ -571,14 +574,10 @@ export const Livechat = {
const { _id } = user;

if (await removeUserFromRolesAsync(_id, ['livechat-agent'])) {
await Users.setOperator(_id, false);
await Users.removeLivechatData(_id);
await this.setUserStatusLivechat(_id, 'not-available');

await Promise.all([
Users.removeAgent(_id),
LivechatDepartmentAgents.removeByAgentId(_id),
LivechatVisitors.removeContactManagerByUsername(username),
Users.unsetExtension(_id),
]);
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export const RoutingManager: Routing = {

if (!agent) {
logger.debug(`No agents available. Unable to delegate inquiry ${inquiry._id}`);
// When an inqury reaches here on CE, it will stay here as 'ready' since on CE there's no mechanism to re queue it.
// When reaching this point, managers have to manually transfer the inquiry to another room. This is expected.
return LivechatRooms.findOneById(rid);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Logger } from '../../../../server/lib/logger/Logger';

export const callbackLogger = new Logger('[Omnichannel] Callback');
export const businessHourLogger = new Logger('Business Hour');
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/sendMessageBySMS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { OmnichannelIntegration } from '@rocket.chat/core-services';
import { callbacks } from '../../../lib/callbacks';
import { settings } from '../../settings/server';
import { normalizeMessageFileUpload } from '../../utils/server/functions/normalizeMessageFileUpload';
import { callbackLogger } from './lib/callbackLogger';
import { callbackLogger } from './lib/logger';

callbacks.add(
'afterSaveMessage',
Expand Down
8 changes: 6 additions & 2 deletions apps/meteor/app/livechat/server/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ Meteor.startup(async () => {
await createDefaultBusinessHourIfNotExists();

settings.watch<boolean>('Livechat_enable_business_hours', async (value) => {
Livechat.logger.debug(`Changing business hour type to ${value}`);
if (value) {
return businessHourManager.startManager();
await businessHourManager.startManager();
Livechat.logger.debug(`Business hour manager started`);
return;
}
return businessHourManager.stopManager();
await businessHourManager.stopManager();
Livechat.logger.debug(`Business hour manager stopped`);
});

settings.watch<string>('Livechat_Routing_Method', function (value) {
Expand Down
Loading

0 comments on commit 668057d

Please sign in to comment.