@tiledesk/tiledesk-server 2.19.16 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,10 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.20.0
9
+ - Updated tybot-connector to 2.1.8
10
+ - Improved flow attributes management
11
+
8
12
  # 2.19.16
9
13
  - Queued /kb/scrape/single via scheduleScrape instead of sync startScrape
10
14
  - Improved guest closure event handling in activity monitoring
package/jobs.js CHANGED
@@ -84,7 +84,14 @@ async function main()
84
84
  //************* LOAD CHAT21 ************ //
85
85
  channelManager.listen(); // chat21Handler is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler
86
86
 
87
-
87
+ // Same Redis client as app.js — required by routing-queue (flow attributes cache delete)
88
+ const { TdCache } = require('./utils/TdCache');
89
+ let tdCache = new TdCache({
90
+ host: process.env.CACHE_REDIS_HOST,
91
+ port: process.env.CACHE_REDIS_PORT,
92
+ password: process.env.CACHE_REDIS_PASSWORD
93
+ });
94
+ tdCache.connect();
88
95
 
89
96
 
90
97
  let jobsManager = new JobsManager(undefined, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued, updateRequestSnapshotQueued);
@@ -102,7 +109,10 @@ async function main()
102
109
 
103
110
 
104
111
  let routingQueueQueued = require('./pubmodules/routing-queue').listenerQueued;
105
- winston.debug("routingQueueQueued");
112
+ winston.debug("routingQueueQueued");
113
+ if (tdCache) {
114
+ routingQueueQueued.setTdCache(tdCache);
115
+ }
106
116
  jobsManager.listenRoutingQueue(routingQueueQueued);
107
117
 
108
118
  let whatsappQueue = require('@tiledesk/tiledesk-whatsapp-jobworker');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-server",
3
3
  "description": "The Tiledesk server module",
4
- "version": "2.19.16",
4
+ "version": "2.20.0",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
@@ -49,7 +49,7 @@
49
49
  "@tiledesk/tiledesk-rasa-connector": "^1.0.10",
50
50
  "@tiledesk/tiledesk-sms-connector": "^0.1.13",
51
51
  "@tiledesk/tiledesk-telegram-connector": "^0.1.14",
52
- "@tiledesk/tiledesk-tybot-connector": "^2.1.5",
52
+ "@tiledesk/tiledesk-tybot-connector": "^2.1.8",
53
53
  "@tiledesk/tiledesk-voice-twilio-connector": "^0.3.2",
54
54
  "@tiledesk/tiledesk-vxml-connector": "^0.1.91",
55
55
  "@tiledesk/tiledesk-whatsapp-connector": "1.0.26",
@@ -689,6 +689,9 @@ class PubModulesManager {
689
689
  }
690
690
  if (this.routingQueueQueued) {
691
691
  try {
692
+ if (this.tdCache) {
693
+ this.routingQueueQueued.setTdCache(this.tdCache);
694
+ }
692
695
  // this.routingQueueQueued.listen();
693
696
  this.jobsManager.listenRoutingQueue(this.routingQueueQueued);
694
697
  winston.info("PubModulesManager routingQueue queued started");
@@ -6,6 +6,7 @@ var Request = require('../../models/request')
6
6
  var winston = require('../../config/winston');
7
7
 
8
8
  var ProjectUserUtil = require("../../utils/project_userUtil");
9
+ const requestService = require('../../services/requestService');
9
10
 
10
11
  // var request = require('retry-request', {
11
12
  // request: require('request')
@@ -28,23 +29,36 @@ var ProjectUserUtil = require("../../utils/project_userUtil");
28
29
  // TODO web socket is not supported with queue
29
30
  class Listener {
30
31
 
31
-
32
+
32
33
  constructor() {
33
34
  this.enabled = true;
34
- if (process.env.ROUTE_QUEUE_ENABLED=="false" || process.env.ROUTE_QUEUE_ENABLED==false) {
35
- this.enabled = false;
35
+ this.tdCache = null;
36
+ if (process.env.ROUTE_QUEUE_ENABLED == "false" || process.env.ROUTE_QUEUE_ENABLED == false) {
37
+ this.enabled = false;
36
38
  }
37
- winston.debug("Listener this.enabled: "+ this.enabled);
39
+ winston.debug("Listener this.enabled: " + this.enabled);
38
40
  }
39
41
 
40
-
42
+ /**
43
+ * Reuse app's TdCache instance instead of opening a new Redis connection.
44
+ * Injected by PubModulesManager.
45
+ * @param {TdCache} tdCache - shared Redis cache from app.get('redis_client')
46
+ */
47
+ setTdCache(tdCache) {
48
+ if (tdCache && typeof tdCache.del === 'function') {
49
+ this.tdCache = tdCache;
50
+ winston.debug("Route queue Listener using shared TdCache for flow attributes");
51
+ }
52
+ }
53
+
54
+
41
55
  // db.getCollection('project_users').find({"number_assigned_requests" : {"$lt":0}}).count()
42
56
 
43
57
  // New version of updateProjectUser() method.
44
58
  // This will not increment or decrement the number_assigned_requests field but search the exact number of assigned conversation to the project user
45
59
  updateProjectUser(id_user, id_project, operation) {
46
60
  // winston.debug("Route queue updateProjectUser start operation: " + operation + "id_user " + id_user + " id_project " + id_project);
47
-
61
+
48
62
  // escludi id_user che iniziano con bot_
49
63
  if (id_user.startsWith('bot_')) {
50
64
  return winston.warn("Chatbot is not a project_user. Skip update.")
@@ -64,27 +78,27 @@ class Listener {
64
78
  // winston.debug("Route queue number_assigned_requests +1 :" + updatedPU.id);
65
79
  // winston.debug("Route queue number_assigned_requests +1 :" + updatedPU.id);
66
80
  winston.debug("Route queue number_assigned_requests updated to " + requestsCount + "for project user " + updatedPU.id);
67
-
81
+
68
82
  updatedPU.populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1 } }, function (err, updatedProject_userPopulated) {
69
-
83
+
70
84
  var pu = updatedProject_userPopulated.toJSON();
71
-
85
+
72
86
  return Project.findById(id_project).exec(function (err, project) {
73
87
  pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, project.settings && project.settings.max_agent_assigned_chat);
74
88
  winston.debug("Route queue pu.isBusy: " + pu.isBusy);
75
-
89
+
76
90
  authEvent.emit('project_user.update', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true }); //if queued with jobs -> websocket notification on project_user.update doesn't work??? forse si in quanto viene convertito in .pub.queue e poi rifunziiona
77
-
78
-
91
+
92
+
79
93
  // project_user.update triggers activityArchiver(tested), cache invalidation(tested), subscriptionNotifierQueued and websocket(tested works from queue i trigger ws)
80
94
  if (requestEvent.queueEnabled) { //force to .queue to be catched into the queue (activity archiver, subscriptionNotifierQueued )
81
95
  authEvent.emit('project_user.update.queue', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true });
82
96
  }
83
-
97
+
84
98
  })
85
-
99
+
86
100
  });
87
-
101
+
88
102
  });
89
103
  })
90
104
 
@@ -123,141 +137,184 @@ class Listener {
123
137
  });
124
138
  }
125
139
 
126
- updateParticipatingProjectUsers(request, operation) {
127
- winston.debug("Route queue request.participatingAgents", request.participatingAgents);
128
- if (request.participatingAgents.length>0) {
129
- request.participatingAgents.forEach(user => {
130
- winston.debug("request.participatingAgents user",user); //it is a user and not a project_user
131
- var userid = user.id || user._id;
132
- winston.debug("updateParticipatingProjectUsers userid: "+userid);
133
-
134
- this.updateProjectUser(userid, request.id_project, operation);
135
- });
136
- }
137
- }
138
-
139
- listen() {
140
-
141
- if (this.enabled==true) {
142
- winston.info("Route queue with queue Listener listen");
143
- } else {
144
- return winston.info("Route queue with queue Listener disabled");
140
+ updateParticipatingProjectUsers(request, operation) {
141
+ winston.debug("Route queue request.participatingAgents", request.participatingAgents);
142
+ if (request.participatingAgents.length > 0) {
143
+ request.participatingAgents.forEach(user => {
144
+ winston.debug("request.participatingAgents user", user); //it is a user and not a project_user
145
+ var userid = user.id || user._id;
146
+ winston.debug("updateParticipatingProjectUsers userid: " + userid);
147
+
148
+ this.updateProjectUser(userid, request.id_project, operation);
149
+ });
150
+ }
151
+ }
152
+
153
+ async saveFlowAttributes(request) {
154
+ try {
155
+ if (!request || !request.request_id) {
156
+ return winston.warn("saveFlowAttributes: missing request_id");
157
+ }
158
+ /**
159
+ * The parameters are taken from the chatbot, which already contains the logic to filter out system parameters.
160
+ * For deletion, you can act directly on Redis.
161
+ */
162
+ let parameters = await requestService.getRequestParametersFromChatbot(request.request_id);
163
+ if (!parameters || Object.keys(parameters).length === 0) {
164
+ return;
145
165
  }
166
+ await Request.updateOne(
167
+ { request_id: request.request_id, id_project: request.id_project },
168
+ { $set: { 'attributes.flowAttributes': parameters } }
169
+ );
170
+ winston.debug("Flow attributes saved for request: " + request.request_id);
146
171
 
147
- var that = this;
148
-
149
- // TODO fai versione che passa anche project
150
- var requestCreateKey = 'request.create';
151
- if (requestEvent.queueEnabled) {
152
- requestCreateKey = 'request.create.queue';
153
- }
154
- winston.debug('Route queue requestCreateKey: ' + requestCreateKey);
155
-
156
- requestEvent.on(requestCreateKey, async (request) => {
157
- setImmediate(() => {
158
- winston.debug('Route queue requestCreate');
159
- this.updateParticipatingProjectUsers(request, +1);
160
- });
161
- });
172
+ await this.deleteFlowAttributesFromCache(request);
162
173
 
163
- // TODO usa versione complete con project per evitare query??
164
- var requestCloseKey = 'request.close'; //request.close event here queued under job
165
- if (requestEvent.queueEnabled) {
166
- requestCloseKey = 'request.close.queue';
167
- }
168
- winston.debug('Route queue requestCloseKey: ' + requestCloseKey);
174
+ } catch (err) {
175
+ winston.error("Error saving flow attributes: ", err);
176
+ }
177
+ }
169
178
 
170
- requestEvent.on(requestCloseKey, async (request) => { //request.close event here noqueued
171
- winston.debug("request.close event here 4")
172
- setImmediate(() => {
173
- winston.debug('Route queue requestClose');
174
- this.updateParticipatingProjectUsers(request, -1);
175
- });
176
- });
179
+ async deleteFlowAttributesFromCache(request) {
180
+ if (!this.tdCache) {
181
+ return winston.warn("deleteFlowAttributesFromCache: tdCache not set");
182
+ }
183
+ if (!request || !request.request_id) {
184
+ return winston.warn("deleteFlowAttributesFromCache: missing request_id");
185
+ }
186
+ const key = `tilebot:requests:${request.request_id}:parameters`;
187
+ try {
188
+ await this.tdCache.del(key);
189
+ winston.debug("Flow attributes cache deleted: " + key);
190
+ } catch (err) {
191
+ winston.error("Error deleting flow attributes from cache: ", err);
192
+ }
193
+ }
177
194
 
195
+ listen() {
178
196
 
179
- var requestParticipantsJoinKey = 'request.participants.join';
180
- if (requestEvent.queueEnabled) {
181
- requestParticipantsJoinKey = 'request.participants.join.queue';
182
- }
183
- winston.debug('Route queue requestParticipantsJoinKey: ' + requestParticipantsJoinKey);
184
-
185
- requestEvent.on(requestParticipantsJoinKey, async (data) => {
186
- winston.debug('Route queue ParticipantsJoin');
187
-
188
- var request = data.request;
189
- var member = data.member;
190
- setImmediate(() => {
191
- this.updateProjectUser(member, request.id_project, 1);
192
- });
193
- });
197
+ if (this.enabled == true) {
198
+ winston.info("Route queue with queue Listener listen");
199
+ } else {
200
+ return winston.info("Route queue with queue Listener disabled");
201
+ }
194
202
 
195
- var requestParticipantsLeaveKey = 'request.participants.leave';
196
- if (requestEvent.queueEnabled) {
197
- requestParticipantsLeaveKey = 'request.participants.leave.queue';
198
- }
199
- winston.debug('Route queue requestParticipantsLeaveKey: ' + requestParticipantsLeaveKey);
200
-
201
- requestEvent.on(requestParticipantsLeaveKey, async (data) => {
202
- winston.debug('Route queue ParticipantsLeave');
203
-
204
- var request = data.request;
205
- var member = data.member;
206
- setImmediate(() => {
207
- this.updateProjectUser(member, request.id_project, -1);
208
- });
209
- });
203
+ var that = this;
210
204
 
211
- var requestParticipantsUpdateKey = 'request.participants.update';
212
- if (requestEvent.queueEnabled) {
213
- requestParticipantsUpdateKey = 'request.participants.update.queue';
214
- }
215
- winston.debug('Route queue requestParticipantsUpdateKey: ' + requestParticipantsUpdateKey);
216
-
217
- requestEvent.on(requestParticipantsUpdateKey, async (data) => {
218
- winston.debug('Route queue Participants Update');
205
+ // TODO fai versione che passa anche project
206
+ var requestCreateKey = 'request.create';
207
+ if (requestEvent.queueEnabled) {
208
+ requestCreateKey = 'request.create.queue';
209
+ }
210
+ winston.debug('Route queue requestCreateKey: ' + requestCreateKey);
219
211
 
220
- var request = data.request;
221
- var removedParticipants = data.removedParticipants;
222
- var addedParticipants = data.addedParticipants;
212
+ requestEvent.on(requestCreateKey, async (request) => {
213
+ setImmediate(() => {
214
+ winston.debug('Route queue requestCreate');
215
+ this.updateParticipatingProjectUsers(request, +1);
216
+ });
217
+ });
223
218
 
224
- setImmediate(() => {
219
+ // TODO usa versione complete con project per evitare query??
220
+ var requestCloseKey = 'request.close'; //request.close event here queued under job
221
+ if (requestEvent.queueEnabled) {
222
+ requestCloseKey = 'request.close.queue';
223
+ }
224
+ winston.debug('Route queue requestCloseKey: ' + requestCloseKey);
225
+
226
+ requestEvent.on(requestCloseKey, async (request) => { //request.close event here noqueued
227
+ winston.debug("request.close event here 4")
228
+ setImmediate(() => {
229
+ winston.debug('Route queue requestClose');
230
+ this.updateParticipatingProjectUsers(request, -1);
231
+ this.saveFlowAttributes(request);
232
+ });
233
+ });
225
234
 
226
- addedParticipants.forEach(participant => {
227
- winston.debug('addedParticipants participant', participant);
228
- this.updateProjectUser(participant, request.id_project, 1);
229
- });
230
235
 
231
- removedParticipants.forEach(participant => {
232
- winston.debug('removedParticipants participant', participant);
233
- this.updateProjectUser(participant, request.id_project, -1);
234
- });
236
+ var requestParticipantsJoinKey = 'request.participants.join';
237
+ if (requestEvent.queueEnabled) {
238
+ requestParticipantsJoinKey = 'request.participants.join.queue';
239
+ }
240
+ winston.debug('Route queue requestParticipantsJoinKey: ' + requestParticipantsJoinKey);
235
241
 
236
- });
242
+ requestEvent.on(requestParticipantsJoinKey, async (data) => {
243
+ winston.debug('Route queue ParticipantsJoin');
244
+
245
+ var request = data.request;
246
+ var member = data.member;
247
+ setImmediate(() => {
248
+ this.updateProjectUser(member, request.id_project, 1);
249
+ });
250
+ });
251
+
252
+ var requestParticipantsLeaveKey = 'request.participants.leave';
253
+ if (requestEvent.queueEnabled) {
254
+ requestParticipantsLeaveKey = 'request.participants.leave.queue';
255
+ }
256
+ winston.debug('Route queue requestParticipantsLeaveKey: ' + requestParticipantsLeaveKey);
257
+
258
+ requestEvent.on(requestParticipantsLeaveKey, async (data) => {
259
+ winston.debug('Route queue ParticipantsLeave');
260
+
261
+ var request = data.request;
262
+ var member = data.member;
263
+ setImmediate(() => {
264
+ this.updateProjectUser(member, request.id_project, -1);
265
+ });
266
+ });
267
+
268
+ var requestParticipantsUpdateKey = 'request.participants.update';
269
+ if (requestEvent.queueEnabled) {
270
+ requestParticipantsUpdateKey = 'request.participants.update.queue';
271
+ }
272
+ winston.debug('Route queue requestParticipantsUpdateKey: ' + requestParticipantsUpdateKey);
273
+
274
+ requestEvent.on(requestParticipantsUpdateKey, async (data) => {
275
+ winston.debug('Route queue Participants Update');
276
+
277
+ var request = data.request;
278
+ var removedParticipants = data.removedParticipants;
279
+ var addedParticipants = data.addedParticipants;
280
+
281
+ setImmediate(() => {
282
+
283
+ addedParticipants.forEach(participant => {
284
+ winston.debug('addedParticipants participant', participant);
285
+ this.updateProjectUser(participant, request.id_project, 1);
237
286
  });
238
287
 
239
- var requestWorkingStatusUpdateKey = 'request.workingStatus.update';
240
- if (requestEvent.queueEnabled) {
241
- requestWorkingStatusUpdateKey = 'request.workingStatus.update.queue';
242
- }
243
- winston.debug('Route queue requestWorkingStatusUpdateKey: ' + requestWorkingStatusUpdateKey);
244
-
245
- requestEvent.on(requestWorkingStatusUpdateKey, async (data) => {
246
- winston.debug('Route queue WorkingStatus Update');
247
-
248
- var request = data.request;
249
- var participantIds = (request.participantsAgents && request.participantsAgents.length)
250
- ? request.participantsAgents
251
- : (request.participatingAgents || []).map(u => u._id || u.id);
252
- setImmediate(() => {
253
- participantIds.forEach(id_user => {
254
- if (id_user && !String(id_user).startsWith('bot_')) {
255
- this.updateProjectUser(id_user, request.id_project, 0);
256
- }
257
- });
258
- });
288
+ removedParticipants.forEach(participant => {
289
+ winston.debug('removedParticipants participant', participant);
290
+ this.updateProjectUser(participant, request.id_project, -1);
259
291
  });
292
+
293
+ });
294
+ });
295
+
296
+ var requestWorkingStatusUpdateKey = 'request.workingStatus.update';
297
+ if (requestEvent.queueEnabled) {
298
+ requestWorkingStatusUpdateKey = 'request.workingStatus.update.queue';
260
299
  }
300
+ winston.debug('Route queue requestWorkingStatusUpdateKey: ' + requestWorkingStatusUpdateKey);
301
+
302
+ requestEvent.on(requestWorkingStatusUpdateKey, async (data) => {
303
+ winston.debug('Route queue WorkingStatus Update');
304
+
305
+ var request = data.request;
306
+ var participantIds = (request.participantsAgents && request.participantsAgents.length)
307
+ ? request.participantsAgents
308
+ : (request.participatingAgents || []).map(u => u._id || u.id);
309
+ setImmediate(() => {
310
+ participantIds.forEach(id_user => {
311
+ if (id_user && !String(id_user).startsWith('bot_')) {
312
+ this.updateProjectUser(id_user, request.id_project, 0);
313
+ }
314
+ });
315
+ });
316
+ });
317
+ }
261
318
 
262
319
  }
263
320
 
package/routes/kb.js CHANGED
@@ -240,7 +240,7 @@ router.post('/scrape/single', async (req, res) => {
240
240
  json.content = kb.content;
241
241
  }
242
242
 
243
- if (kb.scrape_type) {
243
+ if (kb.scrape_type !== null) {
244
244
  json.scrape_type = kb.scrape_type
245
245
  }
246
246
 
package/routes/request.js CHANGED
@@ -2479,12 +2479,17 @@ router.get('/:requestid/chatbot/parameters', async (req, res) => {
2479
2479
  return res.status(401).send({ success: false, message: "Request does not belong to the project"})
2480
2480
  }
2481
2481
 
2482
- requestService.getRequestParametersFromChatbot(request_id).then((parameters) => {
2482
+ requestService.getRequestParameters(request_id).then((parameters) => {
2483
2483
  res.status(200).send(parameters);
2484
2484
 
2485
2485
  }).catch((err) => {
2486
- console.error("err: ", err.response)
2487
- res.status(400).send(err);
2486
+ if (err.message) {
2487
+ winston.error("Error getting request flow attributes: ", err.message);
2488
+ res.status(400).send({ success: false, message: err.message });
2489
+ } else {
2490
+ winston.error("err: ", err);
2491
+ res.status(400).send({ success: false, message: "Error getting request flow attributes" });
2492
+ }
2488
2493
  })
2489
2494
 
2490
2495
  })
@@ -2478,7 +2478,27 @@ class RequestService {
2478
2478
  });
2479
2479
  }
2480
2480
 
2481
+ async getRequestParameters(request_id) {
2482
+ try {
2483
+ let parameters = await this.getRequestParametersFromChatbot(request_id);
2484
+ if (parameters && Object.keys(parameters).length > 0) {
2485
+ return parameters;
2486
+ }
2487
+ } catch (err) {
2488
+ winston.warn("Error getting request parameters from chatbot, falling back to DB: ", err.message || err);
2489
+ }
2490
+ return await this.getRequestParametersFromDB(request_id);
2491
+ }
2481
2492
 
2493
+ async getRequestParametersFromDB(request_id) {
2494
+ try {
2495
+ let request = await Request.findOne({ request_id: request_id }).select('attributes.flowAttributes').exec();
2496
+ return request?.attributes?.flowAttributes || {};
2497
+ } catch (err) {
2498
+ winston.error("Error getting request parameters from DB: ", err);
2499
+ throw new Error("Error getting request flow attributes");
2500
+ }
2501
+ }
2482
2502
 
2483
2503
  async getRequestParametersFromChatbot(request_id) {
2484
2504