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 <reinaldonetof@hotmail.com>
Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com>
This commit is contained in:
Gleidson Daniel Silva 2023-09-05 09:20:06 -03:00 committed by GitHub
parent 780f426031
commit c31cb9b124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 21 deletions

View File

@ -3,6 +3,6 @@ export type SubscriptionsEndpoints = {
POST: (params: { firstUnreadMessage: { _id: string } } | { roomId: string }) => {}; POST: (params: { firstUnreadMessage: { _id: string } } | { roomId: string }) => {};
}; };
'subscriptions.read': { 'subscriptions.read': {
POST: (params: { rid: string }) => {}; POST: (params: { rid: string; readThreads?: boolean }) => {};
}; };
}; };

View File

@ -254,6 +254,8 @@ const debouncedUpdate = (subscription: ISubscription) => {
if (batch[key]) { if (batch[key]) {
if (/SUB/.test(key)) { if (/SUB/.test(key)) {
const sub = batch[key] as ISubscription; 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 roomQueueId = getRoomQueueId(sub.rid);
const room = batch[roomQueueId] as IRoom; const room = batch[roomQueueId] as IRoom;
delete batch[roomQueueId]; delete batch[roomQueueId];

View File

@ -1,26 +1,28 @@
import { import {
IAvatarSuggestion,
IMessage, IMessage,
INotificationPreferences, INotificationPreferences,
IPreviewItem, IPreviewItem,
IProfileParams,
IRoom, IRoom,
IRoomNotifications, IRoomNotifications,
SubscriptionType, IServerRoom,
IUser, IUser,
IAvatarSuggestion,
IProfileParams,
RoomType, RoomType,
IServerRoom SubscriptionType
} from '../../definitions'; } from '../../definitions';
import { TParams } from '../../definitions/ILivechatEditView';
import { ILivechatTag } from '../../definitions/ILivechatTag';
import { ISpotlight } from '../../definitions/ISpotlight'; import { ISpotlight } from '../../definitions/ISpotlight';
import { TEAM_TYPE } from '../../definitions/ITeam'; 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 { 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 { RoomTypes, roomTypeToApiType, unsubscribeRooms } from '../methods';
import sdk from './sdk';
import { compareServerVersion, getBundleId, isIOS } from '../methods/helpers'; 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 = ({ export const createChannel = ({
name, name,
@ -310,11 +312,33 @@ export const setReaction = (emoji: string, messageId: string) =>
// RC 0.62.2 // RC 0.62.2
sdk.post('chat.react', { emoji, messageId }); sdk.post('chat.react', { emoji, messageId });
export const toggleRead = (read: boolean, roomId: string) => { /**
if (read) { * Toggles the read status of a room.
return sdk.post('subscriptions.unread', { roomId }); *
* @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<ResultFor<'POST', keyof SubscriptionsEndpoints>> => {
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 = ( export const getRoomCounters = (

View File

@ -56,7 +56,8 @@ import {
isRead, isRead,
debounce, debounce,
isIOS, isIOS,
isTablet isTablet,
compareServerVersion
} from '../../lib/methods/helpers'; } from '../../lib/methods/helpers';
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants'; import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
@ -103,6 +104,7 @@ interface IRoomsListViewProps {
createPublicChannelPermission: []; createPublicChannelPermission: [];
createPrivateChannelPermission: []; createPrivateChannelPermission: [];
createDiscussionPermission: []; createDiscussionPermission: [];
serverVersion: string;
} }
interface IRoomsListViewState { interface IRoomsListViewState {
@ -703,9 +705,11 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
toggleRead = async (rid: string, tIsRead: boolean) => { toggleRead = async (rid: string, tIsRead: boolean) => {
logEvent(tIsRead ? events.RL_UNREAD_CHANNEL : events.RL_READ_CHANNEL); logEvent(tIsRead ? events.RL_UNREAD_CHANNEL : events.RL_READ_CHANNEL);
const { serverVersion } = this.props;
try { try {
const db = database.active; 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) { if (result.success) {
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
@ -715,6 +719,9 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
await subRecord.update(sub => { await subRecord.update(sub => {
sub.alert = tIsRead; sub.alert = tIsRead;
sub.unread = 0; sub.unread = 0;
if (includeThreads) {
sub.tunread = [];
}
}); });
} catch (e) { } catch (e) {
log(e); log(e);
@ -1064,7 +1071,8 @@ const mapStateToProps = (state: IApplicationState) => ({
createDirectMessagePermission: state.permissions['create-d'], createDirectMessagePermission: state.permissions['create-d'],
createPublicChannelPermission: state.permissions['create-c'], createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p'], 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)))); export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView))));

View File

@ -1,11 +1,18 @@
import { by, device, element, expect, waitFor } from 'detox'; 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 { ITestUser, createRandomRoom, createRandomUser, initApi } from '../../helpers/data_setup';
import random from '../../helpers/random'; import random from '../../helpers/random';
const roomId = '64b846e4760e618aa9f91ab7'; const roomId = '64b846e4760e618aa9f91ab7';
function getIndex() {
if (device.getPlatform() === 'android') {
return 1;
}
return 0;
}
const sendMessageOnTranslationTestRoom = async (msg: string): Promise<{ user: ITestUser; msgId: string }> => { const sendMessageOnTranslationTestRoom = async (msg: string): Promise<{ user: ITestUser; msgId: string }> => {
const user = await createRandomUser(); const user = await createRandomUser();
const api = await initApi(user.username, user.password); const api = await initApi(user.username, user.password);
@ -36,22 +43,37 @@ async function navigateToRoom(roomName: string) {
.withTimeout(5000); .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) { export function waitForVisible(id: string) {
return waitFor(element(by.id(id))) return waitFor(element(by.id(id)))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(10000);
} }
export function waitForVisibleTextMatcher(msg: string, textMatcher: TTextMatcher) { export function waitForVisibleTextMatcher(msg: string, textMatcher: TTextMatcher) {
return waitFor(element(by[textMatcher](msg)).atIndex(0)) return waitFor(element(by[textMatcher](msg)).atIndex(0))
.toExist() .toExist()
.withTimeout(5000); .withTimeout(10000);
} }
export function waitForNotVisible(id: string) { export function waitForNotVisible(id: string) {
return waitFor(element(by.id(id))) return waitFor(element(by.id(id)))
.not.toBeVisible() .not.toBeVisible()
.withTimeout(5000); .withTimeout(10000);
} }
describe('Auto Translate', () => { describe('Auto Translate', () => {
@ -97,6 +119,7 @@ describe('Auto Translate', () => {
}); });
it('should see old message not translated before enable auto translate', async () => { 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(oldMessage[languages.default] as string, textMatcher);
await waitForVisibleTextMatcher(attachmentMessage[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 () => { 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(oldMessage[languages.translated] as string, textMatcher);
await waitForVisibleTextMatcher(attachmentMessage[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 () => { it('should see new message translated', async () => {
const randomMatcher = random(); const randomMatcher = random();
const data = await sendMessageOnTranslationTestRoom(`${newMessage[languages.default]} - ${randomMatcher}`); 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 waitForVisibleTextMatcher(`${newMessage[languages.translated]} - ${randomMatcher}`, textMatcher);
await deleteMessageOnTranslationTestRoom(data); await deleteMessageOnTranslationTestRoom(data);
}); });