Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BREAK] Move read-receipts to EE and threads check marks improvements #27074

Merged
merged 54 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
09b52a4
first commit
sampaiodiego Sep 22, 2022
2cab6e0
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Oct 5, 2022
8dda41f
Add Reads model
matheusbsilva137 Oct 14, 2022
4df0834
Add readThread method
matheusbsilva137 Dec 20, 2022
9cd1a3f
Merge remote-tracking branch 'origin/develop' into fix-threads-read-r…
sampaiodiego Dec 20, 2022
a8bf703
fix first thread message as read
sampaiodiego Dec 20, 2022
7838e80
Mark messages as read only when everyone in the room reads it
matheusbsilva137 Jan 3, 2023
8c8a080
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Jan 3, 2023
8f9b674
Fix typecheck error
matheusbsilva137 Jan 3, 2023
62b866c
Move read receipts to EE
matheusbsilva137 Jan 17, 2023
d4fd644
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Jan 23, 2023
9fdaeaa
Fix getReadReceipts method in EE
matheusbsilva137 Jan 24, 2023
4b96159
Remove logs
matheusbsilva137 Jan 26, 2023
11ecc8c
Add EE license check
matheusbsilva137 Jan 27, 2023
030fd7c
Apply requested changes
matheusbsilva137 Jan 27, 2023
cf6ec1d
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Jan 27, 2023
1bf8ba0
Update read receipts on readThreads call
matheusbsilva137 Jan 30, 2023
169f205
Add new strategy to change thread messages to read in large rooms
matheusbsilva137 Jan 30, 2023
6a30123
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Jan 30, 2023
eb702c4
Remove startup import
matheusbsilva137 Jan 30, 2023
cdfdde2
Fix typecheck error
matheusbsilva137 Jan 30, 2023
a31375a
Remove chat.getMessageReadReceipts endpoint tests (TODO - add in a ne…
matheusbsilva137 Jan 31, 2023
1aaefcf
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 1, 2023
2dc9d6f
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 3, 2023
7a5c543
Merge branch 'develop' into fix-threads-read-receipts
sampaiodiego Feb 6, 2023
51ac07f
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 7, 2023
7e6538a
Make read receipts settings visible to community
matheusbsilva137 Feb 8, 2023
44aba3b
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Feb 8, 2023
c8d2d3c
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
matheusbsilva137 Feb 10, 2023
cd50655
Force update cached collection on version upgrade (so as to get the c…
matheusbsilva137 Feb 10, 2023
461d877
Filter unarchived subscriptions on DB query
matheusbsilva137 Feb 10, 2023
3d3ca04
Add ls index in Reads model
matheusbsilva137 Feb 10, 2023
d22b410
Merge branch 'develop' into fix-threads-read-receipts
LucianoPierdona Feb 10, 2023
a9cd2ce
add tests to EE
LucianoPierdona Feb 11, 2023
f576c08
update tests
LucianoPierdona Feb 11, 2023
848bea8
Add ReadsService to core-services package
matheusbsilva137 Feb 11, 2023
dca7967
update test description
LucianoPierdona Feb 11, 2023
40ed6ba
Skip tests when workspace is not EE
matheusbsilva137 Feb 13, 2023
4fd6ec5
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 13, 2023
bc007ef
Run beforeReadMessages callback after checks
matheusbsilva137 Feb 13, 2023
f39d800
Remove try/catch from chat.getMessageReadReceipts endpoint
matheusbsilva137 Feb 13, 2023
e8b2637
Update service and collection names to MessageReads
matheusbsilva137 Feb 13, 2023
0e19cc8
Revert IRoom type change
matheusbsilva137 Feb 13, 2023
a9c9cc0
Update condition to check if room is from omnichannel
matheusbsilva137 Feb 13, 2023
c4e17fc
Merge branch 'develop' into fix-threads-read-receipts
ggazzo Feb 14, 2023
8bb89d2
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 14, 2023
71ba401
Merge branch 'develop' into fix-threads-read-receipts
kodiakhq[bot] Feb 14, 2023
eeb370c
Merge branch 'develop' into fix-threads-read-receipts
kodiakhq[bot] Feb 14, 2023
66c8621
Merge branch 'develop' into fix-threads-read-receipts
kodiakhq[bot] Feb 14, 2023
326abe9
Merge branch 'develop' into fix-threads-read-receipts
kodiakhq[bot] Feb 14, 2023
9460eb1
Merge branch 'develop' into fix-threads-read-receipts
kodiakhq[bot] Feb 14, 2023
2418212
Merge branch 'develop' into fix-threads-read-receipts
sampaiodiego Feb 14, 2023
2646403
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 14, 2023
5dc4c05
Merge branch 'develop' into fix-threads-read-receipts
matheusbsilva137 Feb 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions apps/meteor/app/api/server/v1/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,31 +366,6 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'chat.getMessageReadReceipts',
{ authRequired: true },
{
async get() {
const { messageId } = this.queryParams;
if (!messageId) {
return API.v1.failure({
error: "The required 'messageId' param is missing.",
});
}

try {
return API.v1.success({
receipts: await Meteor.call('getReadReceipts', { messageId }),
});
} catch (error) {
return API.v1.failure({
error: error.message,
});
}
},
},
);

API.v1.addRoute(
'chat.reportMessage',
{ authRequired: true },
Expand Down
17 changes: 17 additions & 0 deletions apps/meteor/app/lib/server/startup/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,23 @@ settingsRegistry.addGroup('Message', function () {
public: true,
});
});
this.section('Read_Receipts', function () {
this.add('Message_Read_Receipt_Enabled', false, {
type: 'boolean',
enterprise: true,
invalidValue: false,
modules: ['message-read-receipt'],
public: true,
});
this.add('Message_Read_Receipt_Store_Users', false, {
type: 'boolean',
enterprise: true,
invalidValue: false,
modules: ['message-read-receipt'],
public: true,
enableQuery: { _id: 'Message_Read_Receipt_Enabled', value: true },
});
});
this.add('Message_AllowEditing', true, {
type: 'boolean',
public: true,
Expand Down
57 changes: 55 additions & 2 deletions apps/meteor/app/models/server/models/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -1059,12 +1059,38 @@ export class Messages extends Base {
return this.findOne(query, options);
}

setAsRead(rid, until) {
setVisibleMessagesAsRead(rid, until) {
return this.update(
{
rid,
unread: true,
ts: { $lt: until },
$or: [
{
tmid: { $exists: false },
},
{
tshow: true,
},
],
},
{
$unset: {
unread: 1,
},
},
{
multi: true,
},
);
}

setThreadMessagesAsRead(tmid, until) {
return this.update(
{
tmid,
unread: true,
ts: { $lt: until },
},
{
$unset: {
Expand All @@ -1090,10 +1116,37 @@ export class Messages extends Base {
);
}

findUnreadMessagesByRoomAndDate(rid, after) {
findVisibleUnreadMessagesByRoomAndDate(rid, after) {
const query = {
unread: true,
rid,
$or: [
{
tmid: { $exists: false },
},
{
tshow: true,
},
],
};

if (after) {
query.ts = { $gt: after };
}

return this.find(query, {
fields: {
_id: 1,
},
});
}

findUnreadThreadMessagesByDate(tmid, userId, after) {
const query = {
'u._id': { $ne: userId },
'unread': true,
tmid,
'tshow': { $exists: false },
};

if (after) {
Expand Down
7 changes: 7 additions & 0 deletions apps/meteor/app/threads/server/methods/getThreadMessages.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';

import { callbacks } from '../../../../lib/callbacks';
import { Messages, Rooms } from '../../../models/server';
import { canAccessRoom } from '../../../authorization/server';
import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -33,13 +34,19 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getThreadMessages' });
}

if (!thread.tcount) {
return [];
}

callbacks.run('beforeReadMessages', thread.rid, user._id);
readThread({ userId: user._id, rid: thread.rid, tmid });

const result = Messages.findVisibleThreadByThreadId(tmid, {
...(skip && { skip }),
...(limit && { limit }),
sort: { ts: -1 },
}).fetch();
callbacks.runAsync('afterReadMessages', room._id, { uid: user._id, tmid });

return [thread, ...result];
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class CachedCollection<T extends object> extends Emitter<{ changed: T; re

public eventType: EventType;

public version = 17;
public version = 18;

public userRelated: boolean;

Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/client/views/admin/info/LicenseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const LicenseCard = (): ReactElement => {
const hasOmnichannel = modules.includes('livechat-enterprise');
const hasAuditing = modules.includes('auditing');
const hasCannedResponses = modules.includes('canned-responses');
const hasReadReceipts = modules.includes('message-read-receipt');

const handleApplyLicense = useMutableCallback(() =>
setModal(
Expand Down Expand Up @@ -64,6 +65,7 @@ const LicenseCard = (): ReactElement => {
<Feature label={t('Auditing')} enabled={hasAuditing} />
<Feature label={t('Canned_Responses')} enabled={hasCannedResponses} />
<Feature label={t('Engagement_Dashboard')} enabled={hasEngagement} />
<Feature label={t('Read_Receipts')} enabled={hasReadReceipts} />
</>
)}
</Margins>
Expand Down
4 changes: 3 additions & 1 deletion apps/meteor/ee/app/license/server/bundles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export type BundleFeature =
| 'device-management'
| 'oauth-enterprise'
| 'federation'
| 'videoconference-enterprise';
| 'videoconference-enterprise'
| 'message-read-receipt';

interface IBundle {
[key: string]: BundleFeature[];
Expand All @@ -36,6 +37,7 @@ const bundles: IBundle = {
'device-management',
'federation',
'videoconference-enterprise',
'message-read-receipt',
],
pro: [],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { IUser, IRoom, IMessage } from '@rocket.chat/core-typings';
import { MessageReads } from '@rocket.chat/core-services';

import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt';
import { callbacks } from '../../../../../lib/callbacks';
import { settings } from '../../../../../app/settings/server';

callbacks.add(
'afterReadMessages',
(rid: IRoom['_id'], params: { uid: IUser['_id']; lastSeen?: Date; tmid?: IMessage['_id'] }) => {
if (!settings.get('Message_Read_Receipt_Enabled')) {
return;
}
const { uid, lastSeen, tmid } = params;

if (tmid) {
MessageReads.readThread(uid, tmid);
} else if (lastSeen) {
ReadReceipt.markMessagesAsRead(rid, uid, lastSeen);
}
},
callbacks.priority.MEDIUM,
'message-read-receipt-afterReadMessages',
);
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Subscriptions } from '@rocket.chat/models';
import type { IRoom, IMessage } from '@rocket.chat/core-typings';
import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings';

import { ReadReceipt } from './lib/ReadReceipt';
import { callbacks } from '../../../lib/callbacks';
import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt';
import { callbacks } from '../../../../../lib/callbacks';

callbacks.add(
'afterSaveMessage',
(message, room) => {
(message: IMessage, room: IRoom) => {
// skips this callback if the message was edited
if (message.editedAt) {
if (isEditedMessage(message) && message.editedAt) {
return message;
}

if (room && !room.closedAt) {
if (!isOmnichannelRoom(room) || !room.closedAt) {
// set subscription as read right after message was sent
Promise.await(Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id));
}
Expand All @@ -24,12 +26,3 @@ callbacks.add(
callbacks.priority.MEDIUM,
'message-read-receipt-afterSaveMessage',
);

callbacks.add(
'afterReadMessages',
(rid, { uid, lastSeen }) => {
ReadReceipt.markMessagesAsRead(rid, uid, lastSeen);
},
callbacks.priority.MEDIUM,
'message-read-receipt-afterReadMessages',
);
2 changes: 2 additions & 0 deletions apps/meteor/ee/app/message-read-receipt/server/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './afterReadMessages';
import './afterSaveMessage';
5 changes: 5 additions & 0 deletions apps/meteor/ee/app/message-read-receipt/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { onLicense } from '../../license/server';

onLicense('message-read-receipt', async () => {
await import('./hooks');
});
1 change: 1 addition & 0 deletions apps/meteor/ee/client/startup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import './audit';
import './deviceManagement';
import './engagementDashboard';
import './slashCommands';
import './readReceipt';
34 changes: 34 additions & 0 deletions apps/meteor/ee/client/startup/readReceipt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';

import { settings } from '../../../app/settings/client';
import { MessageAction } from '../../../app/ui-utils/client';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import { messageArgs } from '../../../client/lib/utils/messageArgs';
import ReadReceiptsModal from '../../../client/views/room/modals/ReadReceiptsModal';

Meteor.startup(() => {
Tracker.autorun(() => {
const enabled = settings.get('Message_Read_Receipt_Store_Users');

if (!enabled) {
return MessageAction.removeButton('receipt-detail');
}

MessageAction.addButton({
id: 'receipt-detail',
icon: 'info-circled',
label: 'Info',
context: ['starred', 'message', 'message-mobile', 'threads'],
action(_, props) {
const { message = messageArgs(this).msg } = props;
imperativeModal.open({
component: ReadReceiptsModal,
props: { messageId: message._id, onClose: imperativeModal.close },
});
},
order: 10,
group: 'menu',
});
});
});
1 change: 1 addition & 0 deletions apps/meteor/ee/definition/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
import './v1/engagementDashboard';
import './v1/omnichannel';
import './v1/sessions';
import './v1/chat';
import './v1/roles';
34 changes: 34 additions & 0 deletions apps/meteor/ee/definition/rest/v1/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { IMessage, ReadReceipt } from '@rocket.chat/core-typings';
import Ajv from 'ajv';

const ajv = new Ajv({
coerceTypes: true,
});

type GetMessageReadReceiptsProps = {
messageId: IMessage['_id'];
};

const getMessageReadReceiptsPropsSchema = {
type: 'object',
properties: {
messageId: {
type: 'string',
},
},
required: ['messageId'],
additionalProperties: false,
};

export const isGetMessageReadReceiptsProps = ajv.compile<GetMessageReadReceiptsProps>(getMessageReadReceiptsPropsSchema);

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface Endpoints {
'/v1/chat.getMessageReadReceipts': {
GET: (params: GetMessageReadReceiptsProps) => {
receipts: ReadReceipt[];
};
};
}
}
27 changes: 27 additions & 0 deletions apps/meteor/ee/server/api/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Meteor } from 'meteor/meteor';

import { API } from '../../../app/api/server/api';
import { hasLicense } from '../../app/license/server/license';

API.v1.addRoute(
'chat.getMessageReadReceipts',
{ authRequired: true },
{
async get() {
if (!hasLicense('message-read-receipt')) {
throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');
}

const { messageId } = this.queryParams;
if (!messageId) {
return API.v1.failure({
error: "The required 'messageId' param is missing.",
});
}

return API.v1.success({
receipts: await Meteor.call('getReadReceipts', { messageId }),
});
},
},
);
1 change: 1 addition & 0 deletions apps/meteor/ee/server/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import './api';
import './ldap';
import './licenses';
import './sessions';
import './chat';
import './roles';
Loading