diff --git a/apps/meteor/app/utils/client/getRoomAvatarURL.ts b/apps/meteor/app/utils/client/getRoomAvatarURL.ts new file mode 100644 index 0000000000000..8f7dd9dec5337 --- /dev/null +++ b/apps/meteor/app/utils/client/getRoomAvatarURL.ts @@ -0,0 +1,17 @@ +import type { IRoom } from '@rocket.chat/core-typings'; + +import { getAvatarURL } from './getAvatarURL'; +import { settings } from '../../settings/client'; + +export const getRoomAvatarURL = ({ roomId, cache = '' }: { roomId: IRoom['_id']; cache: IRoom['avatarETag'] }) => { + const externalSource = (settings.get('Accounts_RoomAvatarExternalProviderUrl') || '').trim().replace(/\/$/, ''); + if (externalSource && typeof externalSource === 'string') { + return externalSource.replace('{roomId}', roomId); + } + + if (!roomId) { + return; + } + + return getAvatarURL({ roomId, cache }); +}; diff --git a/apps/meteor/client/lib/rooms/roomTypes/private.ts b/apps/meteor/client/lib/rooms/roomTypes/private.ts index 7cf087fe772e2..b1d34a92c4ad5 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/private.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/private.ts @@ -7,7 +7,7 @@ import { hasPermission } from '../../../../app/authorization/client'; import { ChatRoom } from '../../../../app/models/client'; import { settings } from '../../../../app/settings/client'; import { getUserPreference } from '../../../../app/utils/client'; -import { getAvatarURL } from '../../../../app/utils/client/getAvatarURL'; +import { getRoomAvatarURL } from '../../../../app/utils/client/getRoomAvatarURL'; import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig'; import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig'; import { getPrivateRoomType } from '../../../../lib/rooms/roomTypes/private'; @@ -87,7 +87,7 @@ roomCoordinator.add( }, getAvatarPath(room) { - return getAvatarURL({ roomId: room._id, cache: room.avatarETag }) as string; + return getRoomAvatarURL({ roomId: room._id, cache: room.avatarETag }); }, getIcon(room) { diff --git a/apps/meteor/client/lib/rooms/roomTypes/public.ts b/apps/meteor/client/lib/rooms/roomTypes/public.ts index 62ae0d74a2400..4dd38eb95881e 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/public.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/public.ts @@ -7,7 +7,7 @@ import { hasAtLeastOnePermission } from '../../../../app/authorization/client'; import { ChatRoom } from '../../../../app/models/client'; import { settings } from '../../../../app/settings/client'; import { getUserPreference } from '../../../../app/utils/client'; -import { getAvatarURL } from '../../../../app/utils/client/getAvatarURL'; +import { getRoomAvatarURL } from '../../../../app/utils/client/getRoomAvatarURL'; import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig'; import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig'; import { getPublicRoomType } from '../../../../lib/rooms/roomTypes/public'; @@ -87,7 +87,7 @@ roomCoordinator.add( }, getAvatarPath(room) { - return getAvatarURL({ roomId: room._id, cache: room.avatarETag }) as string; + return getRoomAvatarURL({ roomId: room._id, cache: room.avatarETag }); }, getIcon(room) { diff --git a/apps/meteor/tests/e2e/avatar-settings.ts b/apps/meteor/tests/e2e/avatar-settings.ts new file mode 100644 index 0000000000000..991372166829c --- /dev/null +++ b/apps/meteor/tests/e2e/avatar-settings.ts @@ -0,0 +1,98 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannel, createTargetPrivateChannel, createDirectMessage } from './utils'; +import { setSettingValueById } from './utils/setSettingValueById'; +import { test, expect } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +const testAvatars = (homeChannel: HomeChannel, channel: string, url: string) => { + test('expect sidebar avatar to have provider prefix', async () => { + expect(homeChannel.sidenav.getSidebarItemByName(channel).locator('img').getAttribute('src')).toBe(url); + }); + + test('expect channel header avatar to have provider prefix', async () => { + await homeChannel.sidenav.openChat(channel); + expect(homeChannel.content.channelHeader.locator('img').getAttribute('src')).toBe(url); + }); + + test('expect channel info avatar to have provider prefix', async () => { + await homeChannel.sidenav.openChat(channel); + expect(homeChannel.content.channelHeader.locator('img').getAttribute('src')).toBe(url); + }); +} + +test.describe('avatar-settings', () => { + let poHomeChannel: HomeChannel; + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test.describe('external avatar provider', () => { + const providerUrlPrefix = 'https://example.com/avatar/'; + + test.beforeAll(async ({ api }) => { + await setSettingValueById(api, 'Accounts_RoomAvatarExternalUrl', `${providerUrlPrefix}{username}`); + await setSettingValueById(api, 'Accounts_AvatarExternalUrl', `${providerUrlPrefix}{username}`); + }); + + test.afterAll(async ({ api }) => { + await setSettingValueById(api, 'Accounts_RoomAvatarExternalUrl', ''); + await setSettingValueById(api, 'Accounts_AvatarExternalUrl', ''); + }) + + test.describe('public channels', () => { + let channelName = ''; + let avatarUrl = ''; + + test.beforeAll(async ({ api }) => { + channelName = await createTargetChannel(api); + avatarUrl = `${providerUrlPrefix}${channelName}`; + }); + + testAvatars(poHomeChannel, channelName, avatarUrl); + }); + + test.describe('private channels', () => { + let channelName = ''; + let avatarUrl = ''; + + test.beforeAll(async ({ api }) => { + channelName = await createTargetPrivateChannel(api); + avatarUrl = `${providerUrlPrefix}${channelName}`; + }); + + testAvatars(poHomeChannel, channelName, avatarUrl); + }); + + test.describe('direct messages', () => { + const channelName = Users.user2.data.username; + const avatarUrl = `${providerUrlPrefix}${channelName}`; + + test.beforeAll(async ({ api }) => { + await createDirectMessage(api); + + // send a message as user 2 + test.use({ storageState: Users.user2.state }); + await poHomeChannel.sidenav.openChat(Users.user1.data.username); + await poHomeChannel.content.sendMessage('hello world'); + + test.use({ storageState: Users.user1.state }); + }); + + testAvatars(poHomeChannel, channelName, avatarUrl); + + test('expect message avatar to have provider prefix', async () => { + expect(poHomeChannel.content.lastUserMessage.locator('img').getAttribute('src')).toBe(avatarUrl); + }); + + test('expect user card avatar to have provider prefix', async () => { + await poHomeChannel.content.lastUserMessage.locator('.rcx-message-header__name-container').click(); + expect(poHomeChannel.content.userCard.locator('img').getAttribute('src')).toBe(avatarUrl); + }); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index 005e21ce57549..e69819a929633 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -9,6 +9,10 @@ export class HomeContent { this.page = page; } + get channelHeader(): Locator { + return this.page.locator('main header'); + } + get inputMessage(): Locator { return this.page.locator('[name="msg"]'); } @@ -41,6 +45,10 @@ export class HomeContent { return this.page.locator('//button[contains(text(), "Join")]'); } + async openRoomInfo(): Promise { + await this.channelHeader.locator('button[data-qa-id="ToolBoxAction-info-circled"]').click(); + } + async joinRoom(): Promise { await this.btnJoinRoom.click(); } @@ -148,8 +156,12 @@ export class HomeContent { return this.page.locator('[data-qa-id="menu-more-actions"]'); } + get userCard(): Locator { + return this.page.locator('[data-qa="UserCard"]'); + } + get linkUserCard(): Locator { - return this.page.locator('[data-qa="UserCard"] a'); + return this.userCard.locator('a'); } get btnContactInformation(): Locator { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index db7821733474b..348c9fc44554b 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -35,6 +35,10 @@ export class HomeSidenav { return this.page.locator('//*[@id="modal-root"]//button[contains(text(), "Create")]'); } + getSidebarItemByName(name: string): Locator { + return this.page.locator(`[data-qa="sidebar-item"][aria-label="${name}"]`); + } + async openAdministrationByLabel(text: string): Promise { await this.page.locator('role=button[name="Administration"]').click(); await this.page.locator(`li.rcx-option >> text="${text}"`).click(); diff --git a/apps/meteor/tests/e2e/utils/create-target-channel.ts b/apps/meteor/tests/e2e/utils/create-target-channel.ts index 2688ec8b22451..c5edd8b526c44 100644 --- a/apps/meteor/tests/e2e/utils/create-target-channel.ts +++ b/apps/meteor/tests/e2e/utils/create-target-channel.ts @@ -1,5 +1,5 @@ import faker from '@faker-js/faker'; -import type { ChannelsCreateProps } from '@rocket.chat/rest-typings'; +import type { ChannelsCreateProps, GroupsCreateProps } from '@rocket.chat/rest-typings'; import type { BaseTest } from './test'; @@ -14,6 +14,13 @@ export async function createTargetChannel(api: BaseTest['api'], options?: Omit): Promise { + const name = faker.datatype.uuid(); + await api.post('/groups.create', { name, ...options }); + + return name; +} + export async function createTargetTeam(api: BaseTest['api']): Promise { const name = faker.datatype.uuid(); await api.post('/teams.create', { name, type: 1, members: ['user2', 'user1'] });