Skip to content

Commit

Permalink
Merge branch 'develop' into fix/e2ee-file-disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Jul 19, 2024
2 parents 030839d + d3a6299 commit c3ee33f
Show file tree
Hide file tree
Showing 40 changed files with 347 additions and 368 deletions.
5 changes: 0 additions & 5 deletions .changeset/cuddly-ravens-swim.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/weak-taxis-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Added handling of attachments in Omnichannel email transcripts. Earlier attachments were being skipped and were being shown as empty space, now it should render the image attachments and should show relevant error message for unsupported attachments.
17 changes: 17 additions & 0 deletions apps/meteor/app/apps/server/converters/cachedFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const cachedFunction = <F extends (...args: any[]) => any>(fn: F) => {
const cache = new Map<string, unknown>();

return ((...args) => {
const cacheKey = JSON.stringify(args);

if (cache.has(cacheKey)) {
return cache.get(cacheKey) as ReturnType<F>;
}

const result = fn(...args);

cache.set(cacheKey, result);

return result;
}) as F;
};
18 changes: 15 additions & 3 deletions apps/meteor/app/apps/server/converters/messages.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Messages, Rooms, Users } from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';

import { cachedFunction } from './cachedFunction';
import { transformMappedData } from './transformMappedData';

export class AppMessagesConverter {
mem = new WeakMap();

constructor(orch) {
this.orch = orch;
}
Expand All @@ -19,6 +22,15 @@ export class AppMessagesConverter {
return undefined;
}

const cache =
this.mem.get(msgObj) ??
new Map([
['room', cachedFunction(this.orch.getConverters().get('rooms').convertById.bind(this.orch.getConverters().get('rooms')))],
['user', cachedFunction(this.orch.getConverters().get('users').convertById.bind(this.orch.getConverters().get('users')))],
]);

this.mem.set(msgObj, cache);

const map = {
id: '_id',
threadId: 'tmid',
Expand All @@ -37,7 +49,7 @@ export class AppMessagesConverter {
token: 'token',
blocks: 'blocks',
room: async (message) => {
const result = await this.orch.getConverters().get('rooms').convertById(message.rid);
const result = await cache.get('room')(message.rid);
delete message.rid;
return result;
},
Expand All @@ -49,7 +61,7 @@ export class AppMessagesConverter {
return undefined;
}

return this.orch.getConverters().get('users').convertById(editedBy._id);
return cache.get('user')(editedBy._id);
},
attachments: async (message) => {
const result = await this._convertAttachmentsToApp(message.attachments);
Expand All @@ -61,7 +73,7 @@ export class AppMessagesConverter {
return undefined;
}

let user = await this.orch.getConverters().get('users').convertById(message.u._id);
let user = await cache.get('user')(message.u._id);

// When the sender of the message is a Guest (livechat) and not a user
if (!user) {
Expand Down
19 changes: 1 addition & 18 deletions apps/meteor/app/apps/server/converters/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { IUser } from '@rocket.chat/core-typings';
import { isEditedMessage, type IMessage } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';

import { cachedFunction } from './cachedFunction';
import { transformMappedData } from './transformMappedData';

// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -18,24 +19,6 @@ interface Orchestrator {
};
}

const cachedFunction = <F extends (...args: any[]) => any>(fn: F) => {
const cache = new Map<string, unknown>();

return ((...args) => {
const cacheKey = JSON.stringify(args);

if (cache.has(cacheKey)) {
return cache.get(cacheKey) as ReturnType<F>;
}

const result = fn(...args);

cache.set(cacheKey, result);

return result;
}) as F;
};

export class AppThreadsConverter implements IAppThreadsConverter {
constructor(
private readonly orch: {
Expand Down
65 changes: 61 additions & 4 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import type {
LivechatDepartmentDTO,
OmnichannelSourceType,
} from '@rocket.chat/core-typings';
import { ILivechatAgentStatus, UserStatus, isOmnichannelRoom } from '@rocket.chat/core-typings';
import { ILivechatAgentStatus, UserStatus, isFileAttachment, isFileImageAttachment, isOmnichannelRoom } from '@rocket.chat/core-typings';
import colors from '@rocket.chat/fuselage-tokens/colors.json';
import { Logger, type MainLogger } from '@rocket.chat/logger';
import {
LivechatDepartment,
Expand All @@ -37,6 +38,7 @@ import {
ReadReceipts,
Rooms,
LivechatCustomField,
Uploads,
} from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -613,6 +615,7 @@ class LivechatClass {
'livechat-started',
'livechat_video_call',
];
const acceptableImageMimeTypes = ['image/jpeg', 'image/png', 'image/jpg'];
const messages = await Messages.findVisibleByRoomIdNotContainingTypesBeforeTs(
rid,
ignoredMessageTypes,
Expand All @@ -623,21 +626,75 @@ class LivechatClass {
);

let html = '<div> <hr>';
await messages.forEach((message) => {
const InvalidFileMessage = `<div style="background-color: ${colors.n100}; text-align: center; border-color: ${
colors.n250
}; border-width: 1px; border-style: solid; border-radius: 4px; padding-top: 8px; padding-bottom: 8px; margin-top: 4px;">${i18n.t(
'This_attachment_is_not_supported',
{ lng: userLanguage },
)}</div>`;

for await (const message of messages) {
let author;
if (message.u._id === visitor._id) {
author = i18n.t('You', { lng: userLanguage });
} else {
author = showAgentInfo ? message.u.name || message.u.username : i18n.t('Agent', { lng: userLanguage });
}

let messageContent = message.msg;
let filesHTML = '';

if (message.attachments && message.attachments?.length > 0) {
messageContent = message.attachments[0].description || '';

for await (const attachment of message.attachments) {
if (!isFileAttachment(attachment)) {
// ignore other types of attachments
continue;
}

if (!isFileImageAttachment(attachment)) {
filesHTML += `<div>${attachment.title || ''}${InvalidFileMessage}</div>`;
continue;
}

if (!attachment.image_type || !acceptableImageMimeTypes.includes(attachment.image_type)) {
filesHTML += `<div>${attachment.title || ''}${InvalidFileMessage}</div>`;
continue;
}

// Image attachment can be rendered in email body
const file = message.files?.find((file) => file.name === attachment.title);

if (!file) {
filesHTML += `<div>${attachment.title || ''}${InvalidFileMessage}</div>`;
continue;
}

const uploadedFile = await Uploads.findOneById(file._id);

if (!uploadedFile) {
filesHTML += `<div>${file.name}${InvalidFileMessage}</div>`;
continue;
}

const uploadedFileBuffer = await FileUpload.getBuffer(uploadedFile);
filesHTML += `<div styles="color: ${colors.n700}; margin-top: 4px; flex-direction: "column";"><p>${file.name}</p><img src="data:${
attachment.image_type
};base64,${uploadedFileBuffer.toString(
'base64',
)}" style="width: 400px; max-height: 240px; object-fit: contain; object-position: 0;"/></div>`;
}
}

const datetime = moment.tz(message.ts, timezone).locale(userLanguage).format('LLL');
const singleMessage = `
<p><strong>${author}</strong> <em>${datetime}</em></p>
<p>${message.msg}</p>
<p>${messageContent}</p>
<p>${filesHTML}</p>
`;
html += singleMessage;
});
}

html = `${html}</div>`;

Expand Down
22 changes: 8 additions & 14 deletions apps/meteor/app/settings/server/SettingsRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,17 @@ export class SettingsRegistry {

const settingFromCodeOverwritten = overwriteSetting(settingFromCode);

const settingOverwrittenDefault = overrideSetting(settingFromCode);

const settingStored = this.store.getSetting(_id);

const settingStoredOverwritten = settingStored && overwriteSetting(settingStored);

const isOverwritten = settingFromCode !== settingFromCodeOverwritten || (settingStored && settingStored !== settingStoredOverwritten);

const updatedSettingAfterApplyingOverwrite = isOverwritten ? settingFromCodeOverwritten : settingOverwrittenDefault;

try {
validateSetting(settingFromCode._id, settingFromCode.type, settingFromCode.value);
} catch (e) {
IS_DEVELOPMENT && SystemLogger.error(`Invalid setting code ${_id}: ${(e as Error).message}`);
}

const isOverwritten = settingFromCode !== settingFromCodeOverwritten || (settingStored && settingStored !== settingStoredOverwritten);

const { _id: _, ...settingProps } = settingFromCodeOverwritten;

if (settingStored && !compareSettings(settingStored, settingFromCodeOverwritten)) {
Expand All @@ -171,9 +166,6 @@ export class SettingsRegistry {
})();

await this.saveUpdatedSetting(_id, updatedProps, removedKeys);

this.store.set(updatedSettingAfterApplyingOverwrite);

return;
}

Expand All @@ -183,8 +175,6 @@ export class SettingsRegistry {
const removedKeys = Object.keys(settingStored).filter((key) => !['_updatedAt'].includes(key) && !overwrittenKeys.includes(key));

await this.saveUpdatedSetting(_id, settingProps, removedKeys);

this.store.set(updatedSettingAfterApplyingOverwrite);
}
return;
}
Expand All @@ -198,9 +188,13 @@ export class SettingsRegistry {
return;
}

await this.model.insertOne(updatedSettingAfterApplyingOverwrite); // no need to emit unless we remove the oplog
const settingOverwrittenDefault = overrideSetting(settingFromCode);

const setting = isOverwritten ? settingFromCodeOverwritten : settingOverwrittenDefault;

await this.model.insertOne(setting); // no need to emit unless we remove the oplog

this.store.set(updatedSettingAfterApplyingOverwrite);
this.store.set(setting);
}

/*
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/tests/e2e/image-gallery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test.describe.serial('Image Gallery', async () => {
});

test.afterAll(async ({ api }) => {
await poHomeChannel.page.close();
await deleteChannel(api, targetChannel);
await deleteChannel(api, targetChannelLargeImage);
});
Expand Down
52 changes: 29 additions & 23 deletions apps/meteor/tests/e2e/messaging.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Page } from '@playwright/test';

import { createAuxContext } from './fixtures/createAuxContext';
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
Expand Down Expand Up @@ -137,36 +139,40 @@ test.describe.serial('Messaging', () => {
await expect(page.locator('[data-qa-type="message"]').last()).toBeFocused();
});

test('expect show "hello word" in both contexts (targetChannel)', async ({ browser }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
const { page } = await createAuxContext(browser, Users.user2);
const auxContext = { page, poHomeChannel: new HomeChannel(page) };
test.describe('Both contexts', () => {
let auxContext: { page: Page; poHomeChannel: HomeChannel };
test.beforeEach(async ({ browser }) => {
const { page } = await createAuxContext(browser, Users.user2);
auxContext = { page, poHomeChannel: new HomeChannel(page) };
});

await auxContext.poHomeChannel.sidenav.openChat(targetChannel);
test.afterEach(async () => {
await auxContext.page.close();
});

await poHomeChannel.content.sendMessage('hello world');
test('expect show "hello word" in both contexts (targetChannel)', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);

await expect(async () => {
await expect(auxContext.poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
}).toPass();
await auxContext.poHomeChannel.sidenav.openChat(targetChannel);

await auxContext.page.close();
});
await poHomeChannel.content.sendMessage('hello world');

test('expect show "hello word" in both contexts (direct)', async ({ browser }) => {
await poHomeChannel.sidenav.openChat('user2');
const { page } = await createAuxContext(browser, Users.user2);
const auxContext = { page, poHomeChannel: new HomeChannel(page) };
await auxContext.poHomeChannel.sidenav.openChat('user1');
await expect(async () => {
await expect(auxContext.poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
}).toPass();
});

await poHomeChannel.content.sendMessage('hello world');
test('expect show "hello word" in both contexts (direct)', async () => {
await poHomeChannel.sidenav.openChat('user2');
await auxContext.poHomeChannel.sidenav.openChat('user1');

await expect(async () => {
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
await expect(auxContext.poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
}).toPass();
await poHomeChannel.content.sendMessage('hello world');

await auxContext.page.close();
await expect(async () => {
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
await expect(auxContext.poHomeChannel.content.lastUserMessageBody).toHaveText('hello world');
}).toPass();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test.describe.serial('OC - Manage Agents', () => {

// Ensure that there is no leftover data even if test fails
test.afterEach(async ({ api }) => {
await await api.delete('/livechat/users/agent/user1');
await api.delete('/livechat/users/agent/user1');
await api.post('/settings/Omnichannel_enable_department_removal', { value: true }).then((res) => expect(res.status()).toBe(200));
await department.delete();
await api.post('/settings/Omnichannel_enable_department_removal', { value: false }).then((res) => expect(res.status()).toBe(200));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faker } from '@faker-js/faker';
import type { Page } from '@playwright/test';

import { createFakeVisitor } from '../../mocks/data';
import { IS_EE } from '../config/constants';
import { createAuxContext } from '../fixtures/createAuxContext';
import { Users } from '../fixtures/userStates';
Expand Down Expand Up @@ -41,10 +41,7 @@ test.describe('omnichannel-auto-onhold-chat-closing', () => {
await agent.poHomeChannel.sidenav.switchStatus('online');

// start a new chat for each test
newVisitor = {
name: faker.person.firstName(),
email: faker.internet.email(),
};
newVisitor = createFakeVisitor();
poLiveChat = new OmnichannelLiveChat(page, api);
await page.goto('/livechat');
await poLiveChat.openLiveChat();
Expand Down
Loading

0 comments on commit c3ee33f

Please sign in to comment.