Skip to content

Commit

Permalink
Merge branch 'feat/single-contact-id' into feat/search-create-edit
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris committed Oct 15, 2024
2 parents f327f82 + 094b155 commit d90f561
Show file tree
Hide file tree
Showing 40 changed files with 867 additions and 133 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-files-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Adds methods to the Apps-Engine to interact with unread messages to enhance message capabilities on Apps.
5 changes: 5 additions & 0 deletions .changeset/four-experts-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Introduces a new featured action on the room header for action buttons using the non-default category to enhance user accessibility.
5 changes: 5 additions & 0 deletions .changeset/gold-falcons-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixes an issue where a thread message was not scrolled to view when it was opened using its link.
5 changes: 5 additions & 0 deletions .changeset/grumpy-lamps-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed Twilio request validation by accepting URLs with query strings
11 changes: 11 additions & 0 deletions .changeset/many-files-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/i18n": patch
"@rocket.chat/ui-composer": minor
---

Adds a warning to inform users they are about to send unencrypted messages in an E2E Encrypted room if they have the `Unencrypted messages in encrypted rooms` setting enabled.




6 changes: 6 additions & 0 deletions .github/actions/build-docker/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ runs:
install: true
NPM_TOKEN: ${{ inputs.NPM_TOKEN }}

- name: Restore turbo build
uses: actions/download-artifact@v4
with:
name: turbo-build
path: .turbo/cache

- run: yarn build
if: inputs.setup == 'true'
shell: bash
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci-code-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ jobs:

- uses: rharkor/[email protected]

- name: Restore turbo build
uses: actions/download-artifact@v4
with:
name: turbo-build
path: .turbo/cache
- name: Cache TypeCheck
uses: actions/cache@v3
if: matrix.check == 'ts'
Expand Down
42 changes: 0 additions & 42 deletions .github/workflows/ci-deploy-gh-pages-preview.yml

This file was deleted.

5 changes: 5 additions & 0 deletions .github/workflows/ci-test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ jobs:

- uses: rharkor/[email protected]

- name: Restore turbo build
uses: actions/download-artifact@v4
with:
name: turbo-build
path: .turbo/cache
- run: yarn build
# if we are testing a PR from a fork, we need to build the docker image at this point
- uses: ./.github/actions/build-docker
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci-test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ jobs:

- uses: rharkor/[email protected]

- name: Restore turbo build
uses: actions/download-artifact@v4
with:
name: turbo-build
path: .turbo/cache
- name: Unit Test
run: yarn testunit

Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,50 @@ jobs:
- name: Build Rocket.Chat Packages
run: yarn build

- name: Store turbo build
uses: actions/upload-artifact@v4
with:
name: turbo-build
path: .turbo/cache
overwrite: true
include-hidden-files: true

deploy-preview:
runs-on: ubuntu-latest
needs: [release-versions, packages-build]
steps:
- uses: actions/checkout@v3

- uses: rharkor/[email protected]
if: github.event.action != 'closed'

- name: Setup NodeJS
uses: ./.github/actions/setup-node
if: github.event.action != 'closed'
with:
node-version: 14.21.3
deno-version: 1.37.1
cache-modules: true
install: true
- name: Restore turbo build
uses: actions/download-artifact@v4
with:
name: turbo-build
path: .turbo/cache
- name: Build
if: github.event.action != 'closed'
run: |
yarn turbo run build-preview
yarn turbo run .:build-preview-move
npx indexifier .preview --html --extensions .html > .preview/index.html
- uses: rossjrw/pr-preview-action@v1
with:
source-dir: .preview
preview-branch: gh-pages
umbrella-dir: pr-preview
action: auto

build:
name: 📦 Meteor Build - coverage
needs: [release-versions, packages-build]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-version-durability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/[email protected].3
uses: actions/[email protected].4
with:
node-version: '20.15.1'

Expand Down
51 changes: 51 additions & 0 deletions apps/meteor/app/apps/server/bridges/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,57 @@ export class AppRoomBridge extends RoomBridge {
return users.map((user: ICoreUser) => userConverter.convertToApp(user));
}

protected async getUnreadByUser(roomId: string, uid: string, options: GetMessagesOptions, appId: string): Promise<Array<IMessageRaw>> {
this.orch.debugLog(`The App ${appId} is getting the unread messages for the user: "${uid}" in the room: "${roomId}"`);

const messageConverter = this.orch.getConverters()?.get('messages');
if (!messageConverter) {
throw new Error('Message converter not found');
}

const subscription = await Subscriptions.findOneByRoomIdAndUserId(roomId, uid, { projection: { ls: 1 } });

if (!subscription) {
const errorMessage = `No subscription found for user with ID "${uid}" in room with ID "${roomId}". This means the user is not subscribed to the room.`;
this.orch.debugLog(errorMessage);
throw new Error('User not subscribed to room');
}

const lastSeen = subscription?.ls;
if (!lastSeen) {
return [];
}

const sort: Sort = options.sort?.createdAt ? { ts: options.sort.createdAt } : { ts: 1 };

const cursor = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, lastSeen, new Date(), [], {
...options,
sort,
});

const messages = await cursor.toArray();
return Promise.all(messages.map((msg) => messageConverter.convertMessageRaw(msg)));
}

protected async getUserUnreadMessageCount(roomId: string, uid: string, appId: string): Promise<number> {
this.orch.debugLog(`The App ${appId} is getting the unread messages count of the room: "${roomId}" for the user: "${uid}"`);

const subscription = await Subscriptions.findOneByRoomIdAndUserId(roomId, uid, { projection: { ls: 1 } });

if (!subscription) {
const errorMessage = `No subscription found for user with ID "${uid}" in room with ID "${roomId}". This means the user is not subscribed to the room.`;
this.orch.debugLog(errorMessage);
throw new Error('User not subscribed to room');
}

const lastSeen = subscription?.ls;
if (!lastSeen) {
return 0;
}

return Messages.countVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, lastSeen, new Date(), []);
}

protected async removeUsers(roomId: string, usernames: Array<string>, appId: string): Promise<void> {
this.orch.debugLog(`The App ${appId} is removing users ${usernames} from room id: ${roomId}`);
if (!roomId) {
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/authorization/server/constant/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export const permissions = [
_id: 'view-livechat-contact',
roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
},
{
_id: 'view-livechat-contact-history',
roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'],
},
{ _id: 'view-livechat-manager', roles: ['livechat-manager', 'livechat-monitor', 'admin'] },
{
_id: 'view-omnichannel-contact-center',
Expand Down
23 changes: 22 additions & 1 deletion apps/meteor/app/livechat/server/api/v1/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
isPOSTOmnichannelContactsProps,
isPOSTUpdateOmnichannelContactsProps,
isGETOmnichannelContactsProps,
isGETOmnichannelContactHistoryProps,
isGETOmnichannelContactsSearchProps,
} from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';
Expand All @@ -11,7 +12,7 @@ import { Meteor } from 'meteor/meteor';

import { API } from '../../../../api/server';
import { getPaginationItems } from '../../../../api/server/helpers/getPaginationItems';
import { Contacts, createContact, updateContact, getContacts, isSingleContactEnabled } from '../../lib/Contacts';
import { getContactHistory, Contacts, createContact, updateContact, getContacts, isSingleContactEnabled } from '../../lib/Contacts';

API.v1.addRoute(
'omnichannel/contact',
Expand Down Expand Up @@ -161,3 +162,23 @@ API.v1.addRoute(
},
},
);

API.v1.addRoute(
'omnichannel/contacts.history',
{ authRequired: true, permissionsRequired: ['view-livechat-contact-history'], validateParams: isGETOmnichannelContactHistoryProps },
{
async get() {
if (!isSingleContactEnabled()) {
return API.v1.unauthorized();
}

const { contactId, source } = this.queryParams;
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();

const history = await getContactHistory({ contactId, source, count, offset, sort });

return API.v1.success(history);
},
},
);
63 changes: 61 additions & 2 deletions apps/meteor/app/livechat/server/lib/Contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
Subscriptions,
LivechatContacts,
} from '@rocket.chat/models';
import type { PaginatedResult } from '@rocket.chat/rest-typings';
import type { PaginatedResult, VisitorSearchChatsResult } from '@rocket.chat/rest-typings';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import type { MatchKeysAndValues, OnlyFieldsOfType, Sort } from 'mongodb';
import type { MatchKeysAndValues, OnlyFieldsOfType, FindOptions, Sort } from 'mongodb';

import { callbacks } from '../../../../lib/callbacks';
import { trim } from '../../../../lib/utils/stringUtils';
Expand Down Expand Up @@ -72,6 +72,14 @@ type GetContactsParams = {
sort: Sort;
};

type GetContactHistoryParams = {
contactId: string;
source?: string;
count: number;
offset: number;
sort: Sort;
};

export const Contacts = {
async registerContact({
token,
Expand Down Expand Up @@ -283,6 +291,57 @@ export async function getContacts(params: GetContactsParams): Promise<PaginatedR
};
}

export async function getContactHistory(
params: GetContactHistoryParams,
): Promise<PaginatedResult<{ history: VisitorSearchChatsResult[] }>> {
const { contactId, source, count, offset, sort } = params;

const contact = await LivechatContacts.findOneById<Pick<ILivechatContact, 'channels'>>(contactId, { projection: { channels: 1 } });

if (!contact) {
throw new Error('error-contact-not-found');
}

const visitorsIds = new Set(contact.channels?.map((channel: ILivechatContactChannel) => channel.visitorId));

if (!visitorsIds?.size) {
return { history: [], count: 0, offset, total: 0 };
}

const options: FindOptions<IOmnichannelRoom> = {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
projection: {
fname: 1,
ts: 1,
v: 1,
msgs: 1,
servedBy: 1,
closedAt: 1,
closedBy: 1,
closer: 1,
tags: 1,
source: 1,
},
};

const { totalCount, cursor } = LivechatRooms.findPaginatedRoomsByVisitorsIdsAndSource({
visitorsIds: Array.from(visitorsIds),
source,
options,
});

const [total, history] = await Promise.all([totalCount, cursor.toArray()]);

return {
history,
count: history.length,
offset,
total,
};
}

async function getAllowedCustomFields(): Promise<Pick<ILivechatCustomField, '_id' | 'label' | 'regexp' | 'required'>[]> {
return LivechatCustomField.findByScope(
'visitor',
Expand Down
Loading

0 comments on commit d90f561

Please sign in to comment.