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

[NEW] [EE] PDF Chat transcript for Omnichannel conversations #27572

Merged
merged 68 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
a6f4b91
create services
KevLehman Dec 12, 2022
f3374b6
update templates
KevLehman Dec 12, 2022
86aa019
update templates 2
KevLehman Dec 12, 2022
d635d22
update templates 3
KevLehman Dec 12, 2022
fd80b07
make port number different per service generated
KevLehman Dec 12, 2022
b211f44
Chore: Create services for transcript (#27523)
KevLehman Dec 14, 2022
b71b236
refactor: move quickaction to posContent and add divider
filipemarins Dec 6, 2022
411750c
Merge branch 'develop' into feat/chat-transcript
Dec 16, 2022
cfced2d
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
KevLehman Dec 20, 2022
a41ed40
Merge branch 'develop' into feat/chat-transcript
KevLehman Dec 20, 2022
31952b1
[NEW] Implement Queue service (#27531)
KevLehman Dec 22, 2022
efff114
Merge branch 'develop' into feat/chat-transcript
KevLehman Dec 27, 2022
073c4a2
[NEW] Add export as PDF and send via email options (#27579)
Jan 2, 2023
51a0dd9
Merge branch 'develop' into feat/chat-transcript
filipemarins Jan 6, 2023
454e737
Merge branch 'develop' into feat/chat-transcript
murtaza98 Jan 19, 2023
19ca41d
Merge branch 'develop' into feat/chat-transcript
murtaza98 Jan 20, 2023
85f61de
Update lock file
murtaza98 Jan 20, 2023
ef60939
[NEW] PDF Worker service (#27568)
KevLehman Jan 23, 2023
d296755
refactor: remove request button when chat is close (#27768)
Jan 24, 2023
65561f4
Merge branch 'develop' into feat/chat-transcript
KevLehman Jan 30, 2023
3a80cd3
Fix clipboard attachments
KevLehman Jan 30, 2023
8636d76
[NEW] Add files component to chat transcript template (#27750)
Jan 30, 2023
147e143
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
KevLehman Jan 30, 2023
2f7cbf3
[IMPROVE] Add interpreter to message md (#27938)
Feb 2, 2023
bdfb5ce
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
KevLehman Feb 2, 2023
663f3f6
[NEW] Add omnichannel preferences for chat transcript (#27824)
Feb 2, 2023
c85f8be
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 3, 2023
9a73713
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
KevLehman Feb 3, 2023
817484f
fix: merge conflicts
filipemarins Feb 6, 2023
e09fe66
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 7, 2023
8bbcfc6
refactor: unsubscribe listerners when chat is closed (#27932)
Feb 7, 2023
4494fb8
[FIX] PDF is not generated when chat is closed by visitors. (#27964)
murtaza98 Feb 7, 2023
f027369
Chore: add support for monolith (#27948)
KevLehman Feb 7, 2023
492e7f7
heheh
KevLehman Feb 7, 2023
1f7e1a7
basic pdf metrics
KevLehman Feb 8, 2023
553b3a8
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 8, 2023
78d35e1
[IMPROVE] Move omnichannel quick actions to outside kebab menu (#27943)
Feb 8, 2023
43d8bf9
asdf
KevLehman Feb 8, 2023
01a3210
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 8, 2023
a734a11
update close text
KevLehman Feb 8, 2023
ec4aa97
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
KevLehman Feb 8, 2023
e33ae91
[FIX] Clicking on view full icon navigates user to Home Screen (#27977)
Feb 9, 2023
6a19daa
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 10, 2023
6a4c5ed
[FIX] Transcript email is not sent if chat is closed by visitor (#27976)
murtaza98 Feb 10, 2023
818b57c
[IMPROVE] Add styles for inline and multi-line code (#27986)
Feb 10, 2023
db19c6e
Respect user
KevLehman Feb 10, 2023
2cfa751
Merge branch 'develop' into feat/chat-transcript
Feb 10, 2023
fe30ea5
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 11, 2023
9dc083f
oops
KevLehman Feb 11, 2023
8feb63a
wrong
KevLehman Feb 11, 2023
ab1d7ce
[FIX] Hide generated PDF on Livechat upon chat close (#28011)
murtaza98 Feb 13, 2023
f37201a
refactor: changes request
filipemarins Feb 13, 2023
c5acb35
refactor: move conditional to const
filipemarins Feb 13, 2023
86c9811
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 13, 2023
4b75ade
refactor: move form and add handle submit
filipemarins Feb 13, 2023
85bc179
Merge branch 'develop' into feat/chat-transcript
Feb 13, 2023
1558f2e
Merge branch 'develop' into feat/chat-transcript
sampaiodiego Feb 13, 2023
3556c34
Merge branch 'develop' into feat/chat-transcript
Feb 13, 2023
cfeeaea
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 14, 2023
508815b
move pdfworker to ee
KevLehman Feb 14, 2023
b5a0f1b
move service-classes to omnichannel-services
KevLehman Feb 14, 2023
4cc3ecb
Merge branch 'develop' into feat/chat-transcript
murtaza98 Feb 14, 2023
f6aaa42
Merge branch 'feat/chat-transcript' of github.com:RocketChat/Rocket.C…
sampaiodiego Feb 14, 2023
0901811
Merge branch 'develop' into feat/chat-transcript
sampaiodiego Feb 14, 2023
2073f38
fix TS errors
sampaiodiego Feb 14, 2023
883c742
simplify tsconfig
sampaiodiego Feb 14, 2023
c621e9c
Merge branch 'develop' into feat/chat-transcript
KevLehman Feb 14, 2023
1bfcb6d
Merge branch 'develop' into feat/chat-transcript
KevLehman 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
5 changes: 5 additions & 0 deletions .hygen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
helpers: {
random: () => Math.floor(3000 + (5000 - 3000) * Math.random()),
},
};
5 changes: 4 additions & 1 deletion _templates/service/new/package.json.ejs.t
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ to: ee/apps/<%= name %>/package.json
"main": "./dist/ee/apps/<%= name %>/src/service.js",
"files": [
"/dist"
]
],
"volta": {
"node": "14.19.3"
}
}

2 changes: 1 addition & 1 deletion _templates/service/new/service.ejs.t
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { broker } from '../../../../apps/meteor/ee/server/startup/broker';
import { Collections, getCollection, getConnection } from '../../../../apps/meteor/ee/server/services/mongo';
import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels';

const PORT = process.env.PORT || 3034;
const PORT = process.env.PORT || <%= h.random() %>;

(async () => {
const db = await getConnection();
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/app/apps/server/bridges/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getRoom } from '../../../livechat/server/api/lib/livechat';
import { Livechat } from '../../../livechat/server/lib/Livechat';
import { Users, LivechatDepartment, LivechatRooms } from '../../../models/server';
import type { AppServerOrchestrator } from '../orchestrator';
import { Livechat as LivechatTyped } from '../../../livechat/server/lib/LivechatTyped';

export class AppLivechatBridge extends LivechatBridge {
// eslint-disable-next-line no-empty-function
Expand Down Expand Up @@ -122,7 +123,9 @@ export class AppLivechatBridge extends LivechatBridge {
...(visitor && { visitor }),
};

return Livechat.closeRoom(closeData);
await LivechatTyped.closeRoom(closeData);

return true;
}

protected async findRooms(visitor: IVisitor, departmentId: string | null, appId: string): Promise<Array<ILivechatRoom>> {
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/app/file-upload/server/lib/FileUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,11 @@ export class FileUploadClass {
streamOrBuffer = Promise.await(streamToBuffer(streamOrBuffer));
}

if (streamOrBuffer instanceof Uint8Array) {
// Services compat :)
streamOrBuffer = Buffer.from(streamOrBuffer);
}

// Check if the fileData matches store filter
const filter = this.store.getFilter();
if (filter && filter.check) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import type { IUser } from '@rocket.chat/core-typings';

import { LivechatRooms } from '../../../models/server';
import { settings } from '../../../settings/server';
import { Livechat } from '../../../livechat/server/lib/Livechat';
import { Livechat } from '../../../livechat/server/lib/LivechatTyped';

type SubscribedRooms = {
rid: string;
t: string;
};

export const closeOmnichannelConversations = (user: IUser, subscribedRooms: SubscribedRooms[]): void => {
export const closeOmnichannelConversations = async (user: IUser, subscribedRooms: SubscribedRooms[]): Promise<void> => {
const roomsInfo = LivechatRooms.findByIds(subscribedRooms.map(({ rid }) => rid));
const language = settings.get<string>('Language') || 'en';
const comment = TAPi18n.__('Agent_deactivated', { lng: language });

const promises: Promise<void>[] = [];
roomsInfo.forEach((room: any) => {
Livechat.closeRoom({ user, visitor: {}, room, comment });
promises.push(Livechat.closeRoom({ user, room, comment }));
});
KevLehman marked this conversation as resolved.
Show resolved Hide resolved

await Promise.all(promises);
};
9 changes: 7 additions & 2 deletions apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ export function setUserActiveStatus(userId: string, active: boolean, confirmReli
throw new Meteor.Error('user-last-owner', '', rooms);
}

closeOmnichannelConversations(user, livechatSubscribedRooms);
Promise.await(relinquishRoomOwnerships(user, chatSubscribedRooms, false));
Promise.await(
// We don't want one killing the other :)
Promise.allSettled([
closeOmnichannelConversations(user, livechatSubscribedRooms),
relinquishRoomOwnerships(user, chatSubscribedRooms, false),
]),
);
}

if (active && !user.active) {
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/app/lib/server/startup/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ settingsRegistry.addGroup('Accounts', function () {
public: true,
i18nLabel: 'Notifications_Sound_Volume',
});

this.add('Accounts_Default_User_Preferences_omnichannelTranscriptEmail', false, {
type: 'boolean',
public: true,
i18nLabel: 'Omnichannel_transcript_email',
});
});

this.section('Avatar', function () {
Expand Down
121 changes: 116 additions & 5 deletions apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { Random } from 'meteor/random';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import type { ILivechatAgent, IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { ILivechatAgent, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings';
import { LivechatVisitors, Users } from '@rocket.chat/models';
import { LivechatVisitors, Users, LivechatRooms as LivechatRoomsRaw, Subscriptions } from '@rocket.chat/models';
import {
isLiveChatRoomForwardProps,
isPOSTLivechatRoomCloseParams,
Expand All @@ -13,20 +13,24 @@ import {
isLiveChatRoomJoinProps,
isPUTLivechatRoomVisitorParams,
isLiveChatRoomSaveInfoProps,
isPOSTLivechatRoomCloseByUserParams,
} from '@rocket.chat/rest-typings';

import { settings as rcSettings } from '../../../../settings/server';
import { Messages, LivechatRooms } from '../../../../models/server';
import { API } from '../../../../api/server';
import { findGuest, findRoom, getRoom, settings, findAgent, onCheckRoomParams } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
import { Livechat as LivechatTyped } from '../../lib/LivechatTyped';
import { normalizeTransferredByData } from '../../lib/Helper';
import { findVisitorInfo } from '../lib/visitors';
import { canAccessRoom, hasPermission } from '../../../../authorization/server';
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { addUserToRoom } from '../../../../lib/server/functions';
import { apiDeprecationLogger } from '../../../../lib/server/lib/deprecationWarningLogger';
import { deprecationWarning } from '../../../../api/server/helpers/deprecationWarning';
import { callbacks } from '../../../../../lib/callbacks';
import type { CloseRoomParams } from '../../lib/LivechatTyped.d';

const isAgentWithInfo = (agentObj: ILivechatAgent | { hiddenInfo: true }): agentObj is ILivechatAgent => !('hiddenInfo' in agentObj);

Expand Down Expand Up @@ -86,6 +90,8 @@ API.v1.addRoute('livechat/room', {
},
});

// Note: use this route if a visitor is closing a room
// If a RC user(like eg agent) is closing a room, use the `livechat/room.closeByUser` route
API.v1.addRoute(
'livechat/room.close',
{ validateParams: isPOSTLivechatRoomCloseParams },
Expand All @@ -110,16 +116,121 @@ API.v1.addRoute(
const language = rcSettings.get<string>('Language') || 'en';
const comment = TAPi18n.__('Closed_by_visitor', { lng: language });

// @ts-expect-error -- typings on closeRoom are wrong
if (!Livechat.closeRoom({ visitor, room, comment })) {
return API.v1.failure();
const options: CloseRoomParams['options'] = {};
if (room.servedBy) {
const servingAgent: Pick<IUser, '_id' | 'name' | 'username' | 'utcOffset' | 'settings' | 'language'> | null =
await Users.findOneById(room.servedBy._id, {
projection: {
name: 1,
username: 1,
utcOffset: 1,
settings: 1,
language: 1,
},
});

if (servingAgent?.settings?.preferences?.omnichannelTranscriptPDF) {
options.pdfTranscript = {
requestedBy: servingAgent._id,
};
}

// We'll send the transcript by email only if the setting is disabled (that means, we're not asking the user if he wants to receive the transcript by email)
// And the agent has the preference enabled to send the transcript by email and the visitor has an email address
// When Livechat_enable_transcript is enabled, the email will be sent via livechat/transcript route
if (
!rcSettings.get<boolean>('Livechat_enable_transcript') &&
servingAgent?.settings?.preferences?.omnichannelTranscriptEmail &&
visitor.visitorEmails?.length &&
visitor.visitorEmails?.[0]?.address
) {
const visitorEmail = visitor.visitorEmails?.[0]?.address;

const language = servingAgent.language || rcSettings.get<string>('Language') || 'en';
const t = (s: string): string => TAPi18n.__(s, { lng: language });
const subject = t('Transcript_of_your_livechat_conversation');

options.emailTranscript = {
sendToVisitor: true,
requestData: {
email: visitorEmail,
requestedAt: new Date(),
requestedBy: servingAgent,
subject,
},
};
}
}

await LivechatTyped.closeRoom({ visitor, room, comment, options });

return API.v1.success({ rid, comment });
},
},
);

API.v1.addRoute(
'livechat/room.closeByUser',
{
validateParams: isPOSTLivechatRoomCloseByUserParams,
authRequired: true,
permissionsRequired: ['close-livechat-room'],
},
{
async post() {
const { rid, comment, tags, generateTranscriptPdf, transcriptEmail } = this.bodyParams;

const room = await LivechatRoomsRaw.findOneById(rid);
if (!room || !isOmnichannelRoom(room)) {
throw new Error('error-invalid-room');
}

if (!room.open) {
throw new Error('error-room-already-closed');
}

const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, this.userId, { projection: { _id: 1 } });
if (!subscription && !(await hasPermissionAsync(this.userId, 'close-others-livechat-room'))) {
throw new Error('error-not-authorized');
}

const options: CloseRoomParams['options'] = {
clientAction: true,
tags,
...(generateTranscriptPdf && { pdfTranscript: { requestedBy: this.userId } }),
...(transcriptEmail && {
...(transcriptEmail.sendToVisitor
? {
emailTranscript: {
sendToVisitor: true,
requestData: {
email: transcriptEmail.requestData.email,
subject: transcriptEmail.requestData.subject,
requestedAt: new Date(),
requestedBy: this.user,
},
},
}
: {
emailTranscript: {
sendToVisitor: false,
},
}),
}),
};

await LivechatTyped.closeRoom({
room,
user: this.user,
options,
comment,
});

return API.v1.success();
},
},
);

API.v1.addRoute(
'livechat/room.transfer',
{ validateParams: isPOSTLivechatRoomTransferParams },
Expand Down
46 changes: 0 additions & 46 deletions apps/meteor/app/livechat/server/hooks/beforeCloseRoom.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,22 @@ const getSecondsSinceLastAgentResponse = async (room, agentLastMessage) => {

callbacks.add(
'livechat.closeRoom',
(room) => {
(params) => {
const { room } = params;

const closedByAgent = room.closer !== 'visitor';
const wasTheLastMessageSentByAgent = room.lastMessage && !room.lastMessage.token;
if (!closedByAgent || !wasTheLastMessageSentByAgent) {
return;
return params;
}
const agentLastMessage = Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, room.v.lastMessageTs);
if (!agentLastMessage) {
return;
return params;
}
const secondsSinceLastAgentResponse = Promise.await(getSecondsSinceLastAgentResponse(room, agentLastMessage));
LivechatRooms.setVisitorInactivityInSecondsById(room._id, secondsSinceLastAgentResponse);

return room;
return params;
},
callbacks.priority.HIGH,
'process-room-abandonment',
Expand Down
Loading