Skip to content

Commit

Permalink
Merge branch 'develop' into fix-catch-error-when-adding-oauth-app
Browse files Browse the repository at this point in the history
  • Loading branch information
julio-rocketchat authored Feb 3, 2025
2 parents ec4e1e1 + 3fda478 commit 7aa49c3
Show file tree
Hide file tree
Showing 48 changed files with 735 additions and 538 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-jeans-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes an issue that allowed departments to be removed via API even with setting `Omnichannel_enable_department_removal` disabled
5 changes: 5 additions & 0 deletions .changeset/funny-ears-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixes a rerender on each sidebar item click
8 changes: 8 additions & 0 deletions .changeset/slow-flies-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": minor
"@rocket.chat/model-typings": minor
---

Replaces Livechat Visitors by Contacts on workspaces' MAC count.
This allows a more accurate and potentially smaller MAC count in case Contact Identification is enabled, since multiple visitors may be associated to the same contact.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Free for 30 days. Afterward, choose between continuing to host on our secure clo

You can follow these instructions to setup a dev environment:

- Install **Node 20.x (LTS)** either [manually](https://nodejs.org/dist/latest-v20.x/) or using a tool like [nvm](https://github.com/creationix/nvm) or [volta](https://volta.sh/) (recommended)
- Install **Node 22.x (LTS)** either [manually](https://nodejs.org/dist/latest-v22.x/) or using a tool like [nvm](https://github.com/creationix/nvm) or [volta](https://volta.sh/) (recommended)
- Install **Meteor** ([version here](apps/meteor/.meteor/release)): https://docs.meteor.com/about/install.html
- Install **yarn**: https://yarnpkg.com/getting-started/install
- Install **Deno 1.x**: https://docs.deno.com/runtime/fundamentals/installation/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LivechatRooms, Statistics, Users } from '@rocket.chat/models';
import { LivechatContacts, Statistics, Users } from '@rocket.chat/models';
import moment from 'moment';

import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function buildWorkspaceRegistrationData<T extends string | undefine
const workspaceType = settings.get<string>('Server_Type');

const seats = await Users.getActiveLocalUserCount();
const [macs] = await LivechatRooms.getMACStatisticsForPeriod(moment.utc().format('YYYY-MM'));
const MAC = await LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM'));

const license = settings.get<string>('Enterprise_License');

Expand Down Expand Up @@ -102,7 +102,7 @@ export async function buildWorkspaceRegistrationData<T extends string | undefine
setupComplete: setupWizardState === 'completed',
connectionDisable: false,
npsEnabled,
MAC: macs?.contactsCount ?? 0,
MAC,
// activeContactsBillingMonth: stats.omnichannelContactsBySource.contactsCount,
// activeContactsYesterday: stats.uniqueContactsOfYesterday.contactsCount,
statsToken: stats.statsToken,
Expand Down
1 change: 0 additions & 1 deletion apps/meteor/app/livechat-enterprise/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { hasLicense } from '../../license/client';
import './startup';

void hasLicense('livechat-enterprise').then((enabled) => {
if (!enabled) {
Expand Down
22 changes: 0 additions & 22 deletions apps/meteor/app/livechat-enterprise/client/startup.ts

This file was deleted.

24 changes: 24 additions & 0 deletions apps/meteor/app/livechat-enterprise/hooks/useLivechatEnterprise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useSetting } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';

import { useHasLicenseModule } from '../../../client/hooks/useHasLicenseModule';
import { businessHourManager } from '../../livechat/client/views/app/business-hours/BusinessHours';
import type { IBusinessHourBehavior } from '../../livechat/client/views/app/business-hours/IBusinessHourBehavior';
import { SingleBusinessHourBehavior } from '../../livechat/client/views/app/business-hours/Single';
import { MultipleBusinessHoursBehavior } from '../client/views/business-hours/Multiple';

const businessHours: Record<string, IBusinessHourBehavior> = {
multiple: new MultipleBusinessHoursBehavior(),
single: new SingleBusinessHourBehavior(),
};

export const useLivechatEnterprise = () => {
const businessHourType = useSetting('Livechat_business_hour_type') as string;
const hasLicense = useHasLicenseModule('livechat-enterprise');

useEffect(() => {
if (businessHourType && hasLicense) {
businessHourManager.registerBusinessHourBehavior(businessHours[businessHourType.toLowerCase()]);
}
}, [businessHourType, hasLicense]);
};
7 changes: 7 additions & 0 deletions apps/meteor/app/livechat/imports/server/rest/departments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Match, check } from 'meteor/check';
import { API } from '../../../../api/server';
import { getPaginationItems } from '../../../../api/server/helpers/getPaginationItems';
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { settings } from '../../../../settings/server';
import {
findDepartments,
findDepartmentById,
Expand Down Expand Up @@ -164,6 +165,12 @@ API.v1.addRoute(
_id: String,
});

const isRemoveEnabled = settings.get<boolean>('Omnichannel_enable_department_removal');

if (!isRemoveEnabled) {
return API.v1.failure('error-department-removal-disabled');
}

await removeDepartment(this.urlParams._id);

return API.v1.success();
Expand Down
8 changes: 4 additions & 4 deletions apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IOmnichannelRoom, IMessage } from '@rocket.chat/core-typings';
import { isEditedMessage, isMessageFromVisitor, isSystemMessage } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { LivechatRooms, LivechatVisitors, LivechatInquiry } from '@rocket.chat/models';
import { LivechatRooms, LivechatContacts, LivechatInquiry } from '@rocket.chat/models';
import moment from 'moment';

import { callbacks } from '../../../../lib/callbacks';
Expand All @@ -17,11 +17,11 @@ export async function markRoomResponded(
}

const monthYear = moment().format('YYYY-MM');
const isVisitorActive = await LivechatVisitors.isVisitorActiveOnPeriod(room.v._id, monthYear);
const isContactActive = await LivechatContacts.isContactActiveOnPeriod({ visitorId: room.v._id, source: room.source }, monthYear);

// Case: agent answers & visitor is not active, we mark visitor as active
if (!isVisitorActive) {
await LivechatVisitors.markVisitorActiveForPeriod(room.v._id, monthYear);
if (!isContactActive) {
await LivechatContacts.markContactActiveForPeriod({ visitorId: room.v._id, source: room.source }, monthYear);
}

if (!room.v?.activity?.includes(monthYear)) {
Expand Down
23 changes: 13 additions & 10 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,6 @@ export const prepareLivechatRoom = async (
const extraRoomInfo = await callbacks.run('livechat.beforeRoom', roomInfo, extraData);
const { _id, username, token, department: departmentId, status = 'online' } = guest;
const newRoomAt = new Date();

const { activity } = guest;
logger.debug({
msg: `Creating livechat room for visitor ${_id}`,
visitor: { _id, username, departmentId, status, activity },
});

const source = extraRoomInfo.source || roomInfo.source;

if (settings.get<string>('Livechat_Require_Contact_Verification') === 'always') {
Expand All @@ -103,14 +96,20 @@ export const prepareLivechatRoom = async (
const contactId = await migrateVisitorIfMissingContact(_id, source);
const contact =
contactId &&
(await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'name' | 'channels'>>(contactId, {
projection: { name: 1, channels: 1 },
(await LivechatContacts.findOneById<Pick<ILivechatContact, '_id' | 'name' | 'channels' | 'activity'>>(contactId, {
projection: { name: 1, channels: 1, activity: 1 },
}));
if (!contact) {
throw new Error('error-invalid-contact');
}
const verified = Boolean(contact.channels.some((channel) => isVerifiedChannelInSource(channel, _id, source)));

const activity = guest.activity || contact.activity;
logger.debug({
msg: `Creating livechat room for visitor ${_id}`,
visitor: { _id, username, departmentId, status, activity },
});

// TODO: Solve `u` missing issue
return {
_id: rid,
Expand Down Expand Up @@ -199,7 +198,11 @@ export const createLivechatInquiry = async ({

const extraInquiryInfo = await callbacks.run('livechat.beforeInquiry', extraData);

const { _id, username, token, department, status = UserStatus.ONLINE, activity } = guest;
const { _id, username, token, department, status = UserStatus.ONLINE } = guest;
const inquirySource = extraData?.source || { type: OmnichannelSourceType.OTHER };
const activity =
guest.activity ||
(await LivechatContacts.findOneByVisitor({ visitorId: guest._id, source: inquirySource }, { projection: { activity: 1 } }))?.activity;

const ts = new Date();

Expand Down
18 changes: 17 additions & 1 deletion apps/meteor/app/livechat/server/lib/contacts/ContactMerger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ContactFields = {
username: string;
manager: ManagerValue;
channel: ILivechatContactChannel;
activity: string[];
};

type CustomFieldAndValue = { type: `customFields.${string}`; value: string };
Expand All @@ -29,6 +30,7 @@ export type FieldAndValue =
| { type: keyof Omit<ContactFields, 'manager' | 'channel'>; value: string }
| { type: 'manager'; value: ManagerValue }
| { type: 'channel'; value: ILivechatContactChannel }
| { type: 'activity'; value: string[] }
| CustomFieldAndValue;

type ConflictHandlingMode = 'conflict' | 'overwrite' | 'ignore';
Expand Down Expand Up @@ -118,7 +120,7 @@ export class ContactMerger {
}

static getAllFieldsFromContact(contact: ILivechatContact): FieldAndValue[] {
const { customFields = {}, name, contactManager } = contact;
const { customFields = {}, name, contactManager, activity } = contact;

const fields = new Set<FieldAndValue>();

Expand All @@ -134,6 +136,10 @@ export class ContactMerger {
fields.add({ type: 'manager', value: { id: contactManager } });
}

if (activity) {
fields.add({ type: 'activity', value: activity });
}

Object.keys(customFields).forEach((key) =>
fields.add({ type: `customFields.${key}`, value: customFields[key] } as CustomFieldAndValue),
);
Expand Down Expand Up @@ -222,6 +228,7 @@ export class ContactMerger {
const newPhones = ContactMerger.getFieldValuesByType(newFields, 'phone');
const newEmails = ContactMerger.getFieldValuesByType(newFields, 'email');
const newChannels = ContactMerger.getFieldValuesByType(newFields, 'channel');
const newActivities = ContactMerger.getFieldValuesByType(newFields, 'activity');
const newNamesOnly = ContactMerger.getFieldValuesByType(newFields, 'name');
const newCustomFields = newFields.filter(({ type }) => type.startsWith('customFields.')) as CustomFieldAndValue[];
// Usernames are ignored unless the contact has no other name
Expand Down Expand Up @@ -254,6 +261,15 @@ export class ContactMerger {
}
}

if (newActivities.length) {
const newActivity = newActivities.shift();
if (newActivity) {
const distinctActivities = new Set([...newActivity, ...(contact.activity || [])]);
const latestActivities = Array.from(distinctActivities).sort().slice(-12);
dataToSet.activity = latestActivities;
}
}

const customFieldsPerName = new Map<string, CustomFieldAndValue[]>();
for (const customField of newCustomFields) {
if (!customFieldsPerName.has(customField.type)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const modelsMock = {
upsertContact: sinon.stub(),
updateContact: sinon.stub(),
findContactMatchingVisitor: sinon.stub(),
findOneByVisitorId: sinon.stub(),
},
'LivechatRooms': {
findNewestByVisitorIdOrToken: sinon.stub(),
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/providers/LayoutProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const LayoutProvider = ({ children }: LayoutProviderProps) => {
showTopNavbarEmbeddedLayout,
sidebar: {
isCollapsed,
toggle: () => setIsCollapsed((isCollapsed) => !isCollapsed),
toggle: isMobile ? () => setIsCollapsed((isCollapsed) => !isCollapsed) : () => undefined,
collapse: () => setIsCollapsed(true),
expand: () => setIsCollapsed(false),
close: () => (isEmbedded ? setIsCollapsed(true) : router.navigate('/home')),
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/client/views/root/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useEscapeKeyStroke } from './hooks/useEscapeKeyStroke';
import { useGoogleTagManager } from './hooks/useGoogleTagManager';
import { useMessageLinkClicks } from './hooks/useMessageLinkClicks';
import { useAnalytics } from '../../../app/analytics/client/loadScript';
import { useLivechatEnterprise } from '../../../app/livechat-enterprise/hooks/useLivechatEnterprise';
import { useNextcloud } from '../../../app/nextcloud/client/useNextcloud';
import { useAnalyticsEventTracking } from '../../hooks/useAnalyticsEventTracking';
import { useLoadRoomForAllowedAnonymousRead } from '../../hooks/useLoadRoomForAllowedAnonymousRead';
Expand All @@ -29,6 +30,7 @@ const AppLayout = () => {
useLoadRoomForAllowedAnonymousRead();
useNotifyUser();

useLivechatEnterprise();
useNextcloud();

const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot);
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/ee/app/license/server/startup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { api } from '@rocket.chat/core-services';
import type { LicenseLimitKind } from '@rocket.chat/core-typings';
import { applyLicense, applyLicenseOrRemove, License } from '@rocket.chat/license';
import { Subscriptions, Users, Settings, LivechatVisitors } from '@rocket.chat/models';
import { Subscriptions, Users, Settings, LivechatContacts } from '@rocket.chat/models';
import { wrapExceptions } from '@rocket.chat/tools';
import moment from 'moment';

Expand Down Expand Up @@ -110,7 +110,7 @@ export const startLicense = async () => {
License.setLicenseLimitCounter('roomsPerGuest', async (context) => (context?.userId ? Subscriptions.countByUserId(context.userId) : 0));
License.setLicenseLimitCounter('privateApps', () => getAppCount('private'));
License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace'));
License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatVisitors.countVisitorsOnPeriod(moment.utc().format('YYYY-MM')));
License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM')));

return new Promise<void>((resolve) => {
// When settings are loaded, apply the current license if there is one.
Expand Down
12 changes: 6 additions & 6 deletions apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/livechat": "workspace:^",
"@rocket.chat/mock-providers": "workspace:^",
"@storybook/addon-a11y": "^8.4.4",
"@storybook/addon-essentials": "^8.4.4",
"@storybook/addon-interactions": "^8.4.4",
"@storybook/addon-a11y": "^8.5.3",
"@storybook/addon-essentials": "^8.5.3",
"@storybook/addon-interactions": "^8.5.3",
"@storybook/addon-styling-webpack": "^1.0.1",
"@storybook/addon-webpack5-compiler-babel": "^3.0.3",
"@storybook/react": "^8.4.4",
"@storybook/react-webpack5": "^8.4.4",
"@storybook/react": "^8.5.3",
"@storybook/react-webpack5": "^8.5.3",
"@testing-library/react": "~16.0.1",
"@testing-library/user-event": "~14.5.2",
"@types/adm-zip": "^0.5.6",
Expand Down Expand Up @@ -203,7 +203,7 @@
"react-docgen-typescript-plugin": "^1.0.8",
"sinon": "^19.0.2",
"source-map": "^0.7.4",
"storybook": "^8.4.4",
"storybook": "^8.5.3",
"stylelint": "^16.10.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/tests/data/livechat/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const fetchInquiry = (roomId: string): Promise<ILivechatInquiryRecord> =>
};

export const createDepartment = (
agents?: { agentId: string }[],
agents?: { agentId: string; count?: number }[],
name?: string,
enabled = true,
opts: Record<string, any> = {},
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,16 @@ describe('LIVECHAT - rooms', () => {
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await updateSetting('Omnichannel_enable_department_removal', true);
await createAgent();
await makeAgentAvailable();
visitor = await createVisitor();

room = await createLivechatRoom(visitor.token);
});
after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
});

describe('livechat/room', () => {
it('should fail when token is not passed as query parameter', async () => {
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('LIVECHAT - Agents', () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await updateSetting('Omnichannel_enable_department_removal', true);
agent = await createAgent();
manager = await createManager();
});
Expand All @@ -55,6 +56,7 @@ describe('LIVECHAT - Agents', () => {
});

after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
await deleteUser(agent2.user);
});

Expand Down
Loading

0 comments on commit 7aa49c3

Please sign in to comment.