fix: Global Search "Jump to message" failing due to incorrect URL generation (#37717)
Some checks are pending
Deploy GitHub Pages / deploy-preview (push) Waiting to run
CI / ⚙️ Variables Setup (push) Waiting to run
CI / 🚀 Notify external services - draft (push) Blocked by required conditions
CI / 📦 Build Packages (push) Blocked by required conditions
CI / 📦 Meteor Build (${{ matrix.type }}) (coverage) (push) Blocked by required conditions
CI / 📦 Meteor Build (${{ matrix.type }}) (production) (push) Blocked by required conditions
CI / 🚢 Build Docker (amd64, [account-service presence-service stream-hub-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Blocked by required conditions
CI / 🚢 Build Docker (arm64, [account-service presence-service stream-hub-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Blocked by required conditions
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Blocked by required conditions
CI / 🚢 Publish Docker Images (ghcr.io) (push) Blocked by required conditions
CI / 📦 Track Image Sizes (push) Blocked by required conditions
CI / 🔎 Code Check (push) Blocked by required conditions
CI / 🔨 Test Storybook (push) Blocked by required conditions
CI / 🔨 Test Unit (push) Blocked by required conditions
CI / 🔨 Test API (CE) (push) Blocked by required conditions
CI / 🔨 Test UI (CE) (push) Blocked by required conditions
CI / 🔨 Test API (EE) (push) Blocked by required conditions
CI / 🔨 Test UI (EE) (push) Blocked by required conditions
CI / 🔨 Test Federation Matrix (push) Blocked by required conditions
CI / 📊 Report Coverage (push) Blocked by required conditions
CI / ✅ Tests Done (push) Blocked by required conditions
CI / 🚀 Publish build assets (push) Blocked by required conditions
CI / 🚀 Publish Docker Images (DockerHub) (push) Blocked by required conditions
CI / 🚀 Notify external services (push) Blocked by required conditions
CI / Update Version Durability (push) Blocked by required conditions
Code scanning - action / CodeQL-Build (push) Waiting to run

Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com>
This commit is contained in:
Abhinav Kumar 2025-12-19 10:01:10 +05:30 committed by GitHub
parent d821cd3ffc
commit 25a10b65bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 129 additions and 26 deletions

View File

@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---
Fixes incorrect URL generation in Global Search "Jump to message" feature, resolving navigation issues when jumping to messages across different channels.

View File

@ -67,18 +67,18 @@ class RoomCoordinatorClient extends RoomCoordinator {
roomType: RoomType,
subData: RoomIdentification,
queryParams?: Record<string, string>,
options: { replace?: boolean } = {},
options: { replace?: boolean; routeParamsOverrides?: Record<string, string> } = {},
): void {
const config = this.getRoomTypeConfig(roomType);
if (!config?.route) {
return;
}
let routeData = {};
let _routeData = {};
if (config.route.link) {
routeData = config.route.link(subData);
_routeData = config.route.link(subData);
} else if (subData?.name) {
routeData = {
_routeData = {
name: subData.name,
};
} else {
@ -88,7 +88,7 @@ class RoomCoordinatorClient extends RoomCoordinator {
router.navigate(
{
pattern: config.route.path ?? '/home',
params: routeData,
params: { ..._routeData, ...options.routeParamsOverrides },
search: queryParams,
},
options,

View File

@ -8,7 +8,12 @@ import { roomCoordinator } from '../rooms/roomCoordinator';
const getRoomById = memoize((rid: IRoom['_id']) => callWithErrorHandling('getRoomById', rid));
export const goToRoomById = async (rid: IRoom['_id']): Promise<void> => {
export type GoToRoomByIdOptions = {
replace?: boolean;
routeParamsOverrides?: Record<string, string>;
};
export const goToRoomById = async (rid: IRoom['_id'], options: GoToRoomByIdOptions = {}): Promise<void> => {
if (!rid) {
return;
}
@ -16,10 +21,10 @@ export const goToRoomById = async (rid: IRoom['_id']): Promise<void> => {
const subscription = Subscriptions.state.find((record) => record.rid === rid);
if (subscription) {
roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters());
roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters(), options);
return;
}
const room = await getRoomById(rid);
roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters());
roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters(), options);
};

View File

@ -4,7 +4,6 @@ import { isThreadMessage } from '@rocket.chat/core-typings';
import { goToRoomById } from './goToRoomById';
import { RoomHistoryManager } from '../../../app/ui-utils/client';
import { router } from '../../providers/RouterProvider';
import { Rooms } from '../../stores';
import { RoomManager } from '../RoomManager';
/** @deprecated */
@ -15,24 +14,12 @@ export const legacyJumpToMessage = async (message: IMessage) => {
if (tab === 'thread' && (context === message.tmid || context === message._id)) {
return;
}
router.navigate(
{
name: router.getRouteName()!,
params: {
tab: 'thread',
context: message.tmid || message._id,
rid: message.rid,
name: Rooms.state.get(message.rid)?.name ?? '',
},
search: {
...router.getSearchParameters(),
msg: message._id,
},
},
{ replace: false },
);
await RoomHistoryManager.getSurroundingMessages(message);
await goToRoomById(message.rid, {
routeParamsOverrides: { tab: 'thread', context: message.tmid || message._id },
replace: RoomManager.opened === message.rid,
});
await RoomHistoryManager.getSurroundingMessages(message);
return;
}

View File

@ -0,0 +1,78 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { Random } from '@rocket.chat/random';
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { setSettingValueById } from './utils';
import type { BaseTest } from './utils/test';
import { expect, test } from './utils/test';
test.use({ storageState: Users.admin.state });
test.describe.serial('Global Search', () => {
let targetChannel: { name: string; _id: string };
let targetGroup: { name: string; _id: string };
let threadMessage: IMessage;
let poHomeChannel: HomeChannel;
const fillMessages = async (api: BaseTest['api']) => {
const { message: parentMessage } = await (
await api.post('/chat.postMessage', { roomId: targetChannel._id, text: 'This is main message in channel' })
).json();
const { message: childMessage } = await (
await api.post('/chat.postMessage', { roomId: targetChannel._id, text: `This is thread message in channel`, tmid: parentMessage._id })
).json();
threadMessage = childMessage;
};
test.beforeAll(async ({ api }) => {
await Promise.all([
api
.post('/channels.create', { name: Random.id() })
.then((res) => res.json())
.then((data) => {
targetChannel = data.channel;
}),
api
.post('/groups.create', {
name: Random.id(),
extraData: { encrypted: false },
})
.then((res) => res.json())
.then((data) => {
targetGroup = data.group;
}),
setSettingValueById(api, 'Search.defaultProvider.GlobalSearchEnabled', true),
]);
await fillMessages(api);
});
test.afterAll(({ api }) =>
Promise.all([
api.post('/channels.delete', { roomId: targetChannel._id }),
api.post('/groups.delete', { roomId: targetGroup._id }),
setSettingValueById(api, 'Search.defaultProvider.GlobalSearchEnabled', false),
]),
);
test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);
await page.goto('/home');
});
test('should open the correct message when jumping from global search in group to channel thread', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetGroup.name);
await poHomeChannel.roomToolbar.btnSearchMessages.click();
await poHomeChannel.tabs.searchMessages.search(threadMessage.msg.slice(10), { global: true }); // fill partial text to match search
const message = await poHomeChannel.tabs.searchMessages.getResultItem(threadMessage.msg);
await message.hover();
const jumpToMessageButton = message.getByRole('button', { name: 'Jump to message' });
await jumpToMessageButton.click();
await expect(page.locator('header').getByRole('button').filter({ hasText: targetChannel.name })).toBeVisible(); // match channel name in room header
await expect(page.getByText(threadMessage.msg)).toBeVisible();
});
});

View File

@ -7,6 +7,7 @@ import { HomeFlextabNotificationPreferences } from './home-flextab-notificationP
import { HomeFlextabOtr } from './home-flextab-otr';
import { HomeFlextabPruneMessages } from './home-flextab-pruneMessages';
import { HomeFlextabRoom } from './home-flextab-room';
import { SearchMessagesFlexTab } from './searchMessages-flextab';
export class HomeFlextab {
private readonly page: Page;
@ -25,6 +26,8 @@ export class HomeFlextab {
readonly pruneMessages: HomeFlextabPruneMessages;
readonly searchMessages: SearchMessagesFlexTab;
constructor(page: Page) {
this.page = page;
this.members = new HomeFlextabMembers(page);
@ -34,6 +37,7 @@ export class HomeFlextab {
this.otr = new HomeFlextabOtr(page);
this.exportMessages = new ExportMessagesTab(page);
this.pruneMessages = new HomeFlextabPruneMessages(page);
this.searchMessages = new SearchMessagesFlexTab(page);
}
get toolbarPrimaryActions(): Locator {

View File

@ -0,0 +1,20 @@
import type { Page } from '@playwright/test';
import { FlexTab } from './flextab';
export class SearchMessagesFlexTab extends FlexTab {
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'Search Messages' }));
}
async search(text: string, { global = false }: { global?: boolean } = {}) {
if (global) {
await this.root.getByText('Global search').click();
}
await this.root.getByPlaceholder('Search Messages').fill(text);
}
async getResultItem(messageText: string) {
return this.root.getByRole('listitem', { name: messageText });
}
}

View File

@ -60,6 +60,10 @@ export class RoomToolbar extends Toolbar {
return this.root.getByRole('button', { name: 'Options' });
}
get btnSearchMessages(): Locator {
return this.root.getByRole('button', { name: 'Search Messages' });
}
get btnDisableE2EEncryption(): Locator {
return this.root.getByRole('button', { name: 'Disable E2E encryption' });
}