From c31cb9b12455ab5502c13117474cdc398424ecb6 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Tue, 5 Sep 2023 09:20:06 -0300 Subject: [PATCH] feat: add `readThreads` params to `subscriptions.read` (#5184) * add readThreads params to subscriptions.read * add tunread verification * fix e2e test * search cat message * fix when grouping by unread * increase timeout --------- Co-authored-by: Reinaldo Neto Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> --- app/definitions/rest/v1/subscriptions.ts | 2 +- app/lib/methods/subscriptions/rooms.ts | 2 + app/lib/services/restApi.ts | 50 ++++++++++++++++++------ app/views/RoomsListView/index.tsx | 14 +++++-- e2e/tests/room/11-autoTranslate.spec.ts | 33 ++++++++++++++-- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/app/definitions/rest/v1/subscriptions.ts b/app/definitions/rest/v1/subscriptions.ts index d02c81099..57f9258f2 100644 --- a/app/definitions/rest/v1/subscriptions.ts +++ b/app/definitions/rest/v1/subscriptions.ts @@ -3,6 +3,6 @@ export type SubscriptionsEndpoints = { POST: (params: { firstUnreadMessage: { _id: string } } | { roomId: string }) => {}; }; 'subscriptions.read': { - POST: (params: { rid: string }) => {}; + POST: (params: { rid: string; readThreads?: boolean }) => {}; }; }; diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 97cf097b9..8a9703c9b 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -254,6 +254,8 @@ const debouncedUpdate = (subscription: ISubscription) => { if (batch[key]) { if (/SUB/.test(key)) { const sub = batch[key] as ISubscription; + // When calling the api subscriptions.read passing readThreads as true it does not return this prop + if (!sub.tunread) sub.tunread = []; const roomQueueId = getRoomQueueId(sub.rid); const room = batch[roomQueueId] as IRoom; delete batch[roomQueueId]; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 509ddf8d3..8bfc9a429 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1,26 +1,28 @@ import { + IAvatarSuggestion, IMessage, INotificationPreferences, IPreviewItem, + IProfileParams, IRoom, IRoomNotifications, - SubscriptionType, + IServerRoom, IUser, - IAvatarSuggestion, - IProfileParams, RoomType, - IServerRoom + SubscriptionType } from '../../definitions'; +import { TParams } from '../../definitions/ILivechatEditView'; +import { ILivechatTag } from '../../definitions/ILivechatTag'; import { ISpotlight } from '../../definitions/ISpotlight'; import { TEAM_TYPE } from '../../definitions/ITeam'; +import { OperationParams, ResultFor } from '../../definitions/rest/helpers'; +import { SubscriptionsEndpoints } from '../../definitions/rest/v1/subscriptions'; import { Encryption } from '../encryption'; -import { TParams } from '../../definitions/ILivechatEditView'; -import { store as reduxStore } from '../store/auxStore'; -import { getDeviceToken } from '../notifications'; import { RoomTypes, roomTypeToApiType, unsubscribeRooms } from '../methods'; -import sdk from './sdk'; import { compareServerVersion, getBundleId, isIOS } from '../methods/helpers'; -import { ILivechatTag } from '../../definitions/ILivechatTag'; +import { getDeviceToken } from '../notifications'; +import { store as reduxStore } from '../store/auxStore'; +import sdk from './sdk'; export const createChannel = ({ name, @@ -310,11 +312,33 @@ export const setReaction = (emoji: string, messageId: string) => // RC 0.62.2 sdk.post('chat.react', { emoji, messageId }); -export const toggleRead = (read: boolean, roomId: string) => { - if (read) { - return sdk.post('subscriptions.unread', { roomId }); +/** + * Toggles the read status of a room. + * + * @param isRead - Whether to mark the room as read or unread. + * @param roomId - The ID of the room. + * @param includeThreads - Optional flag to include threads when marking as read. + * @returns A promise from the sdk post method. + */ +export const toggleReadStatus = ( + isRead: boolean, + roomId: string, + includeThreads?: boolean +): Promise> => { + let endpoint: keyof SubscriptionsEndpoints; + let payload: OperationParams<'POST', keyof SubscriptionsEndpoints> = { roomId }; + + if (isRead) { + endpoint = 'subscriptions.unread'; + } else { + endpoint = 'subscriptions.read'; + payload = { rid: roomId }; + if (includeThreads) { + payload.readThreads = includeThreads; + } } - return sdk.post('subscriptions.read', { rid: roomId }); + + return sdk.post(endpoint, payload); }; export const getRoomCounters = ( diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 24be0f367..cbf72b4d2 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -56,7 +56,8 @@ import { isRead, debounce, isIOS, - isTablet + isTablet, + compareServerVersion } from '../../lib/methods/helpers'; import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants'; import { Services } from '../../lib/services'; @@ -103,6 +104,7 @@ interface IRoomsListViewProps { createPublicChannelPermission: []; createPrivateChannelPermission: []; createDiscussionPermission: []; + serverVersion: string; } interface IRoomsListViewState { @@ -703,9 +705,11 @@ class RoomsListView extends React.Component { logEvent(tIsRead ? events.RL_UNREAD_CHANNEL : events.RL_READ_CHANNEL); + const { serverVersion } = this.props; try { const db = database.active; - const result = await Services.toggleRead(tIsRead, rid); + const includeThreads = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.4.0'); + const result = await Services.toggleReadStatus(tIsRead, rid, includeThreads); if (result.success) { const subCollection = db.get('subscriptions'); @@ -715,6 +719,9 @@ class RoomsListView extends React.Component { sub.alert = tIsRead; sub.unread = 0; + if (includeThreads) { + sub.tunread = []; + } }); } catch (e) { log(e); @@ -1064,7 +1071,8 @@ const mapStateToProps = (state: IApplicationState) => ({ createDirectMessagePermission: state.permissions['create-d'], createPublicChannelPermission: state.permissions['create-c'], createPrivateChannelPermission: state.permissions['create-p'], - createDiscussionPermission: state.permissions['start-discussion'] + createDiscussionPermission: state.permissions['start-discussion'], + serverVersion: state.server.version }); export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView)))); diff --git a/e2e/tests/room/11-autoTranslate.spec.ts b/e2e/tests/room/11-autoTranslate.spec.ts index 311ca28ac..85400aab5 100644 --- a/e2e/tests/room/11-autoTranslate.spec.ts +++ b/e2e/tests/room/11-autoTranslate.spec.ts @@ -1,11 +1,18 @@ import { by, device, element, expect, waitFor } from 'detox'; -import { TTextMatcher, login, navigateToLogin, platformTypes, searchRoom, tapBack, tryTapping } from '../../helpers/app'; +import { TTextMatcher, login, navigateToLogin, platformTypes, searchRoom, sleep, tapBack, tryTapping } from '../../helpers/app'; import { ITestUser, createRandomRoom, createRandomUser, initApi } from '../../helpers/data_setup'; import random from '../../helpers/random'; const roomId = '64b846e4760e618aa9f91ab7'; +function getIndex() { + if (device.getPlatform() === 'android') { + return 1; + } + return 0; +} + const sendMessageOnTranslationTestRoom = async (msg: string): Promise<{ user: ITestUser; msgId: string }> => { const user = await createRandomUser(); const api = await initApi(user.username, user.password); @@ -36,22 +43,37 @@ async function navigateToRoom(roomName: string) { .withTimeout(5000); } +async function searchMessage(msg: string, textMatcher: TTextMatcher) { + await sleep(1000); // wait for proper load the room + await element(by.id('room-view-search')).tap(); + await waitFor(element(by.id('search-messages-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('search-message-view-input')).replaceText(msg); + await waitFor(element(by[textMatcher](msg)).atIndex(getIndex())) + .toExist() + .withTimeout(30000); + await sleep(1000); + await element(by[textMatcher](msg)).atIndex(getIndex()).tap(); + await sleep(10000); +} + export function waitForVisible(id: string) { return waitFor(element(by.id(id))) .toBeVisible() - .withTimeout(5000); + .withTimeout(10000); } export function waitForVisibleTextMatcher(msg: string, textMatcher: TTextMatcher) { return waitFor(element(by[textMatcher](msg)).atIndex(0)) .toExist() - .withTimeout(5000); + .withTimeout(10000); } export function waitForNotVisible(id: string) { return waitFor(element(by.id(id))) .not.toBeVisible() - .withTimeout(5000); + .withTimeout(10000); } describe('Auto Translate', () => { @@ -97,6 +119,7 @@ describe('Auto Translate', () => { }); it('should see old message not translated before enable auto translate', async () => { + await searchMessage(oldMessage[languages.default] as string, textMatcher); await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher); await waitForVisibleTextMatcher(attachmentMessage[languages.default] as string, textMatcher); }); @@ -141,6 +164,7 @@ describe('Auto Translate', () => { }); it('should see old message translated after enable auto translate', async () => { + await searchMessage(oldMessage[languages.default] as string, textMatcher); await waitForVisibleTextMatcher(oldMessage[languages.translated] as string, textMatcher); await waitForVisibleTextMatcher(attachmentMessage[languages.translated] as string, textMatcher); }); @@ -148,6 +172,7 @@ describe('Auto Translate', () => { it('should see new message translated', async () => { const randomMatcher = random(); const data = await sendMessageOnTranslationTestRoom(`${newMessage[languages.default]} - ${randomMatcher}`); + await searchMessage(`${newMessage[languages.default]} - ${randomMatcher}`, textMatcher); // will scroll the messages list to the last one await waitForVisibleTextMatcher(`${newMessage[languages.translated]} - ${randomMatcher}`, textMatcher); await deleteMessageOnTranslationTestRoom(data); });