@tiledesk/tiledesk-server 2.8.0 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
8
  # 2.8.1
9
+ - Added trashed=false in chatbot namespace query
10
+ - Return empty array if no chatbots are using the namespace
11
+ - Enhanced kb context
12
+ - Added enpoint to retrieve all content's chunks
13
+ - Added limit on namespaces
14
+ - Restore beenInvitedNewUser email
15
+
16
+ # 2.8.0
9
17
  - Enable quotas for conversations, tokens, and direct email.
10
18
  - Added namespaces to knowledge base
11
19
  - Updated tybot-connector to 0.2.82
package/deploy.sh CHANGED
@@ -1,5 +1,5 @@
1
1
  git pull
2
- npm version minor
2
+ npm version patch
3
3
  version=`node -e 'console.log(require("./package.json").version)'`
4
4
  echo "version $version"
5
5
 
package/models/request.js CHANGED
@@ -197,6 +197,9 @@ var RequestSchema = new Schema({
197
197
  closed_by: {
198
198
  type: String
199
199
  },
200
+ duration: {
201
+ type: Number
202
+ },
200
203
 
201
204
  tags: [TagSchema],
202
205
 
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.8.0",
4
+ "version": "2.8.1",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
@@ -186,6 +186,11 @@ router.get('/requests/aggregate/status', function(req, res) {
186
186
  winston.debug('req.query.participant', req.query.participant);
187
187
  query.participants = req.query.participant;
188
188
  }
189
+
190
+ if (req.query.channel) {
191
+ winston.debug('req.query.channel', req.query.channel);
192
+ query['channel.name'] = req.query.channel;
193
+ }
189
194
 
190
195
  winston.debug("QueryParams_LastDayCHART:", lastdays,req.query.department_id)
191
196
  winston.debug("Query_LastDayCHART", query)
@@ -632,6 +637,11 @@ router.get('/requests/aggregate/status', function(req, res) {
632
637
  query.participants = req.query.participant;
633
638
  }
634
639
 
640
+ if (req.query.channel) {
641
+ winston.debug('req.query.channel', req.query.channel);
642
+ query['channel.name'] = req.query.channel;
643
+ }
644
+
635
645
 
636
646
  winston.debug("QueryParams_AvgTime:", lastdays,req.query.department_id)
637
647
  winston.debug("Query_AvgTIME", query)
@@ -801,6 +811,10 @@ router.get('/requests/aggregate/status', function(req, res) {
801
811
  query.participants = req.query.participant;
802
812
  }
803
813
 
814
+ if (req.query.channel) {
815
+ winston.debug('req.query.channel', req.query.channel);
816
+ query['channel.name'] = req.query.channel;
817
+ }
804
818
 
805
819
  winston.debug("QueryParams_DurationTIME:", lastdays,req.query.department_id)
806
820
  winston.debug("Query_DurationTIME", query)
@@ -930,7 +944,10 @@ router.get('/requests/aggregate/status', function(req, res) {
930
944
  query.participants = req.query.participant;
931
945
  }
932
946
 
933
-
947
+ if (req.query.channel) {
948
+ winston.debug('req.query.channel', req.query.channel);
949
+ query['channel.name'] = req.query.channel;
950
+ }
934
951
 
935
952
  winston.debug("QueryParams_SatisfactionTIME:", lastdays,req.query.department_id)
936
953
  winston.debug("Query_SatisfactionTIME", query)
@@ -1056,6 +1073,11 @@ router.get('/requests/hasBot/count', function(req, res) {
1056
1073
  query.department= new ObjectId(req.query.department_id);
1057
1074
 
1058
1075
  }
1076
+
1077
+ if (req.query.channel) {
1078
+ winston.debug('req.query.channel', req.query.channel);
1079
+ query['channel.name'] = req.query.channel;
1080
+ }
1059
1081
 
1060
1082
  winston.debug("QueryParams_LastDayCHART:", lastdays,req.query.department_id)
1061
1083
  winston.debug("Query_LastDayCHART", query)
@@ -1410,6 +1432,11 @@ router.get('/messages/count', function(req, res) {
1410
1432
  query.recipient = req.query.recipient;
1411
1433
  }
1412
1434
 
1435
+ if (req.query.channel) {
1436
+ winston.debug('req.query.channel', req.query.channel);
1437
+ query['channel.name'] = req.query.channel;
1438
+ }
1439
+
1413
1440
 
1414
1441
  winston.debug("Query_LastDayCHART", query)
1415
1442
 
package/routes/kb.js CHANGED
@@ -36,9 +36,12 @@ let default_preview_settings = {
36
36
  max_tokens: 128,
37
37
  temperature: 0.7,
38
38
  top_k: 4,
39
- context: "You are an awesome AI Assistant."
39
+ //context: "You are an awesome AI Assistant."
40
+ context: null
40
41
  }
41
42
 
43
+ let default_context = "Answer if and ONLY if the answer is contained in the context provided. If the answer is not contained in the context provided ALWAYS answer with <NOANS>\n{context}"
44
+
42
45
  /**
43
46
  * ****************************************
44
47
  * Proxy Section - Start
@@ -200,10 +203,11 @@ router.post('/qa', async (req, res) => {
200
203
  }
201
204
  }
202
205
 
206
+ // Check if "Advanced Mode" is active. In such case the default_context must be not appended
203
207
  if (data.system_context) {
204
- data.system_context = data.system_context + " {context}";
208
+ data.system_context = data.system_context + " \n" + default_context;
205
209
  }
206
-
210
+
207
211
  openaiService.askNamespace(data).then((resp) => {
208
212
  winston.debug("qa resp: ", resp.data);
209
213
  let answer = resp.data;
@@ -384,11 +388,52 @@ router.get('/namespace/all', async (req, res) => {
384
388
  })
385
389
  })
386
390
 
391
+ router.get('/namespace/:id/chunks/:content_id', async (req, res) => {
392
+
393
+ let project_id = req.projectid;
394
+ let namespace_id = req.params.id;
395
+ let content_id = req.params.content_id;
396
+
397
+ let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
398
+ winston.error("find namespaces error: ", err)
399
+ return res.status(500).send({ success: false, error: err })
400
+ })
401
+
402
+ let namespaceIds = namespaces.map(namespace => namespace.id);
403
+ if (!namespaceIds.includes(namespace_id)) {
404
+ return res.status(403).send({ success: false, error: "Not allowed. The namespace does not belong to the current project." })
405
+ }
406
+
407
+ let content = await KB.find({ id_project: project_id, namespace: namespace_id, _id: content_id }).catch((err) => {
408
+ winston.error("find content error: ", err);
409
+ return res.status(403).send({ success: false, error: err })
410
+ })
411
+
412
+ if(!content) {
413
+ return res.status(403).send({ success: false, error: "Not allowed. The conten does not belong to the current namespace." })
414
+ }
415
+
416
+ openaiService.getContentChunks(namespace_id, content_id).then((resp) => {
417
+ let chunks = resp.data;
418
+ winston.debug("chunks for content " + content_id);
419
+ winston.debug("chunks found ", chunks);
420
+ return res.status(200).send(chunks);
421
+
422
+ }).catch((err) => {
423
+ console.log("error getting content chunks err.response: ", err.response)
424
+ console.log("error getting content chunks err.data: ", err.data)
425
+ return res.status(500).send({ success: false, error: err });
426
+ })
427
+
428
+ })
429
+
387
430
  router.get('/namespace/:id/chatbots', async (req, res) => {
388
431
 
389
432
  let project_id = req.projectid;
390
433
  let namespace_id = req.params.id;
391
434
 
435
+ let chatbotsArray = [];
436
+
392
437
  let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
393
438
  winston.error("find namespaces error: ", err)
394
439
  res.status(500).send({ success: false, error: err })
@@ -406,16 +451,15 @@ router.get('/namespace/:id/chatbots', async (req, res) => {
406
451
 
407
452
  if (!intents || intents.length == 0) {
408
453
  winston.verbose("No intents found for the selected chatbot")
409
- return res.status(200).send({ success: false, message: "No intents found for the selected chatbot" });
454
+ return res.status(200).send(chatbotsArray);
410
455
  }
411
456
 
412
457
  let chatbots = intents.map(i => i.id_faq_kb);
413
458
  let uniqueChatbots = [...new Set(chatbots)];
414
459
 
415
- let chatbotsArray = [];
416
460
  let chatbotPromises = uniqueChatbots.map(async (c_id) => {
417
461
  try {
418
- let chatbot = await faq_kb.findById(c_id);
462
+ let chatbot = await faq_kb.findOne({ _id: c_id, trashed: false });
419
463
  if (chatbot) {
420
464
  let data = {
421
465
  _id: chatbot._id,
@@ -431,7 +475,7 @@ router.get('/namespace/:id/chatbots', async (req, res) => {
431
475
  await Promise.all(chatbotPromises);
432
476
 
433
477
  winston.debug("chatbotsArray: ", chatbotsArray);
434
-
478
+
435
479
  res.status(200).send(chatbotsArray);
436
480
  })
437
481
 
@@ -457,6 +501,15 @@ router.post('/namespace', async (req, res) => {
457
501
  new_namespace.default = true;
458
502
  }
459
503
 
504
+ let quoteManager = req.app.get('quote_manager');
505
+ let limits = await quoteManager.getPlanLimits(req.project);
506
+ let ns_limit = limits.namespace;
507
+ console.log("Limit of namespaces for current plan " + ns_limit);
508
+
509
+ if (namespaces.length >= ns_limit) {
510
+ return res.status(403).send({ success: false, error: "Maximum number of resources reached for the current plan", plan_limit: ns_limit });
511
+ }
512
+
460
513
  new_namespace.save((err, savedNamespace) => {
461
514
  if (err) {
462
515
  winston.error("create namespace error: ", err);
@@ -14,11 +14,11 @@ const emailEvent = require('../event/emailEvent');
14
14
  // }
15
15
 
16
16
  const PLANS_LIST = {
17
- FREE_TRIAL: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 50 }, // same as PREMIUM
18
- SANDBOX: { requests: 200, messages: 0, tokens: 100000, email: 200, chatbots: 2, kbs: 50 },
19
- BASIC: { requests: 800, messages: 0, tokens: 2000000, email: 200, chatbots: 5, kbs: 150},
20
- PREMIUM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 300},
21
- CUSTOM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 1000}
17
+ FREE_TRIAL: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 50 }, // same as PREMIUM
18
+ SANDBOX: { requests: 200, messages: 0, tokens: 100000, email: 200, chatbots: 2, namespace: 1, kbs: 50 },
19
+ BASIC: { requests: 800, messages: 0, tokens: 2000000, email: 200, chatbots: 5, namespace: 1, kbs: 150 },
20
+ PREMIUM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 300 },
21
+ CUSTOM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 1000 }
22
22
  }
23
23
 
24
24
  const typesList = ['requests', 'messages', 'email', 'tokens', 'chatbots', 'kbs']
@@ -162,6 +162,25 @@ class OpenaiService {
162
162
  })
163
163
  }
164
164
 
165
+ getContentChunks(namespace_id, content_id) {
166
+ winston.info("[OPENAI SERVICE] kb endpoint: " + kb_endpoint_train);
167
+ winston.info(kb_endpoint_train + "/id/" + content_id + "/namespace/" + namespace_id)
168
+ return new Promise((resolve, reject) => {
169
+
170
+ axios({
171
+ url: kb_endpoint_train + "/id/" + content_id + "/namespace/" + namespace_id,
172
+ headers: {
173
+ 'Content-Type': 'application/json'
174
+ },
175
+ method: 'GET'
176
+ }).then((resbody) => {
177
+ resolve(resbody)
178
+ }).catch((err) => {
179
+ reject(err)
180
+ })
181
+ })
182
+ }
183
+
165
184
  deleteIndex(data) {
166
185
  winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint_train);
167
186
 
@@ -1266,14 +1266,16 @@ class RequestService {
1266
1266
 
1267
1267
  }
1268
1268
 
1269
- setClosedAtByRequestId(request_id, id_project, closed_at, closed_by) {
1269
+ setClosedAtByRequestId(request_id, id_project, created_at, closed_at, closed_by) {
1270
1270
 
1271
1271
  return new Promise(function (resolve, reject) {
1272
1272
  // winston.debug("request_id", request_id);
1273
1273
  // winston.debug("newstatus", newstatus);
1274
1274
 
1275
+ let duration = Math.abs(new Date(created_at) - new Date(closed_at))
1276
+
1275
1277
  return Request
1276
- .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by }, { new: true, upsert: false })
1278
+ .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by, duration: duration }, { new: true, upsert: false })
1277
1279
  .populate('lead')
1278
1280
  .populate('department')
1279
1281
  .populate('participatingBots')
@@ -1448,7 +1450,7 @@ class RequestService {
1448
1450
  }
1449
1451
 
1450
1452
  // setClosedAtByRequestId(request_id, id_project, closed_at, closed_by)
1451
- return that.setClosedAtByRequestId(request_id, id_project, new Date().getTime(), closed_by).then(function (updatedRequest) {
1453
+ return that.setClosedAtByRequestId(request_id, id_project, request.createdAt, new Date().getTime(), closed_by).then(function (updatedRequest) {
1452
1454
 
1453
1455
  winston.verbose("Request closed with id: " + updatedRequest.id);
1454
1456
  winston.debug("Request closed ", updatedRequest);
@@ -1,235 +1,230 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
-
3
- <html
4
- lang="en"
5
- xmlns="http://www.w3.org/1999/xhtml"
6
- style="
7
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
8
- box-sizing: border-box;
9
- font-size: 14px;
10
- margin: 0;
11
- "
12
- >
13
- <head>
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml"
4
+ style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
5
+
6
+ <head>
14
7
  <meta name="viewport" content="width=device-width" />
15
8
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
16
- <title>New message from Tiledesk</title>
9
+ <title>New email from Tiledesk</title>
17
10
 
18
11
  <style type="text/css">
19
- img {
20
- max-width: 100%;
21
- margin-left: 16px;
22
- text-align: center !important;
23
- }
24
-
25
- img.CToWUd {
26
- margin-bottom: 16px;
27
- max-width: 200px !important;
28
- width: 200px !important;
29
- min-width: 200px !important;
30
- outline: none;
31
- text-decoration: none;
32
- border: none;
33
- height: auto;
34
- margin-left: 0px;
35
- }
36
-
37
- body {
38
- background-color: #e2e7e9;
39
- -webkit-font-smoothing: antialiased;
40
- -webkit-text-size-adjust: none;
41
- width: 100% !important;
42
- height: 100%;
43
- line-height: 1.6em;
44
- padding: 30px 0px;
45
- }
46
-
47
- .box-container {
48
- display: flex;
49
- flex-direction: column;
50
- justify-content: center;
51
- align-items: center;
52
- width: 100%;
53
- margin-bottom: 30px;
54
- /* position: fixed;
55
- top: 90px; */
56
- }
57
-
58
- .box {
59
- background-color: #fff;
60
- width: 600px;
61
- min-height: 400px;
62
- border-radius: 8px;
63
- }
64
-
65
- .header-image {
66
- background-image: url('https://img.freepik.com/vettori-premium/illustrazione-di-progettazione-del-pacchetto-piatto-di-finanza_205077-4142.jpg?w=2000');
67
- background-size: cover;
68
- background-repeat: no-repeat;
69
- height: 200px;
70
- width: 100%;
71
- margin-bottom: 40px;
72
- border-radius: 8px;
73
- }
74
-
75
- .header {
76
- text-align: center;
77
- }
78
-
79
- h1 {
80
- padding: 0px 40px;
81
- }
82
-
83
- .content-body {
84
- padding: 10px 30px;
85
- }
86
-
87
- a {
88
- /* box-sizing: border-box; */
89
- font-size: 16px;
90
- font-weight: 600;
91
- color: #3c3c3c;
92
- text-decoration: none;
93
- margin: 0;
94
- margin-bottom: 10px;
95
- }
96
-
97
- a:hover {
98
- text-decoration: underline;
99
- }
100
-
101
- @media only screen and (max-width: 640px) {
102
- body {
103
- padding: 0 !important;
104
- }
105
-
106
- h1 {
107
- font-weight: 800 !important;
108
- margin: 20px 0 5px !important;
109
- text-align: center !important;
110
- }
111
-
112
- h2 {
113
- font-weight: 800 !important;
114
- margin: 20px 0 5px !important;
115
- }
116
-
117
- h3 {
118
- font-weight: 800 !important;
119
- margin: 20px 0 5px !important;
120
- }
121
-
122
- h4 {
123
- font-weight: 800 !important;
124
- margin: 20px 0 5px !important;
125
- }
126
-
127
- h1 {
128
- font-size: 22px !important;
129
- }
130
-
131
- h2 {
132
- font-size: 18px !important;
12
+ img {
13
+ max-width: 100%;
14
+ margin-left: 16px;
15
+ text-align: center !important;
133
16
  }
134
17
 
135
- h3 {
136
- font-size: 16px !important;
18
+ img.CToWUd {
19
+ margin-bottom: 16px;
20
+ max-width: 200px !important;
21
+ width: 200px !important;
22
+ min-width: 200px !important;
23
+ outline: none;
24
+ text-decoration: none;
25
+ border: none;
26
+ height: auto;
27
+ margin-left: 0px;
137
28
  }
138
29
 
139
- .container {
140
- padding: 0 !important;
141
- width: 100% !important;
142
- }
143
-
144
- .content {
145
- padding: 0 !important;
30
+ body {
31
+ -webkit-font-smoothing: antialiased;
32
+ -webkit-text-size-adjust: none;
33
+ width: 100% !important;
34
+ height: 100%;
35
+ line-height: 1.6em;
146
36
  }
147
37
 
148
- .content-wrap {
149
- padding: 10px !important;
38
+ body {
39
+ background-color: #f6f6f6;
150
40
  }
151
41
 
152
- .invoice {
153
- width: 100% !important;
42
+ @media only screen and (max-width: 640px) {
43
+ body {
44
+ padding: 0 !important;
45
+ }
46
+
47
+ h1 {
48
+ font-weight: 800 !important;
49
+ margin: 20px 0 5px !important;
50
+ text-align: center !important;
51
+ }
52
+
53
+ h2 {
54
+ font-weight: 800 !important;
55
+ margin: 20px 0 5px !important;
56
+ }
57
+
58
+ h3 {
59
+ font-weight: 800 !important;
60
+ margin: 20px 0 5px !important;
61
+ }
62
+
63
+ h4 {
64
+ font-weight: 800 !important;
65
+ margin: 20px 0 5px !important;
66
+ }
67
+
68
+ h1 {
69
+ font-size: 22px !important;
70
+ }
71
+
72
+ h2 {
73
+ font-size: 18px !important;
74
+ }
75
+
76
+ h3 {
77
+ font-size: 16px !important;
78
+ }
79
+
80
+ .container {
81
+ padding: 0 !important;
82
+ width: 100% !important;
83
+ }
84
+
85
+ .content {
86
+ padding: 0 !important;
87
+ }
88
+
89
+ .content-wrap {
90
+ padding: 10px !important;
91
+ }
92
+
93
+ .invoice {
94
+ width: 100% !important;
95
+ }
154
96
  }
155
- }
156
97
  </style>
157
- </head>
158
-
159
- <body>
160
- <!-- <div style="background-color: #ff5757; height: 300px"></div>
161
- <div style="background-color: #ffdfe0; height: 80px"></div> -->
162
- <div class="box-container">
163
- <div class="box">
164
- <div class="header-image"></div>
165
-
166
- <h1 style="text-align: center; color: #333">
167
- Update on monthly resources usage
168
- </h1>
169
-
170
- <div class="content-body">
171
- <p>Dear {{firstname}},</p>
172
-
173
- <p>
174
- This is to inform you that you reached the <b>{{checkpoint}}%</b> of your
175
- monthly quote for <b>{{resource_name}}</b> on your project <b>{{project_name}}</b>.
176
- </p>
177
-
178
- <p>Below is a summary of the monthly resource usage:</p>
179
-
180
- <table
181
- style="width: 100%; border-collapse: collapse; margin-bottom: 20px"
182
- >
183
- <thead style="">
184
- <tr>
185
- <th style="border: 1px solid #ddd; padding: 8px">Resource</th>
186
- <th style="border: 1px solid #ddd; padding: 8px">Quota</th>
187
- <th style="border: 1px solid #ddd; padding: 8px">Usage</th>
188
- </tr>
189
- </thead>
190
- <tbody style="text-align: center">
191
- <tr>
192
- <td style="border: 1px solid #ddd; padding: 8px">
193
- Conversations
194
- </td>
195
- <td style="border: 1px solid #ddd; padding: 8px">{{requests_quote}}</td>
196
- <td style="border: 1px solid #ddd; padding: 8px">{{requests_perc}}%</td>
197
- </tr>
198
- <tr>
199
- <td style="border: 1px solid #ddd; padding: 8px">AI Tokens</td>
200
- <td style="border: 1px solid #ddd; padding: 8px">{{tokens_quote}}</td>
201
- <td style="border: 1px solid #ddd; padding: 8px">{{tokens_perc}}%</td>
202
- </tr>
203
- <tr>
204
- <td style="border: 1px solid #ddd; padding: 8px">
205
- Chatbot Email
206
- </td>
207
- <td style="border: 1px solid #ddd; padding: 8px">{{email_quote}}</td>
208
- <td style="border: 1px solid #ddd; padding: 8px">{{email_perc}}%</td>
209
- </tr>
210
- <!-- Add more rows for other resources as needed -->
211
- </tbody>
212
- </table>
213
- </div>
214
-
215
- <p style="text-align: center; font-size: 14px; color: #777">
216
- This is an automated message. Please do not reply.
217
- </p>
218
-
219
- <div style="text-align: center; margin-top: 20px; padding: 20px">
220
- <img
221
- src="https://tiledesk.com/wp-content/uploads/2023/10/tiledesk-logo.png"
222
- alt="Company Footer Logo"
223
- style="max-width: 160px"
224
- />
225
- </div>
226
-
227
- </div>
228
- </div>
229
-
230
- <div class="box-container">
231
- <a href="http://www.tiledesk.com" class="company-url">Tiledesk.com</a>
232
- <a href="%unsubscribe_url%" class="unsubscribe">Unsubscribe</a>
233
- </div>
234
- </body>
235
- </html>
98
+ </head>
99
+
100
+ <body itemscope itemtype="http://schema.org/EmailMessage"
101
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
102
+ bgcolor="#f6f6f6">
103
+
104
+ <table class="body-wrap"
105
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
106
+ bgcolor="#f6f6f6">
107
+ <tr
108
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
109
+ <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
110
+ valign="top"></td>
111
+ <td class="container" width="600"
112
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
113
+ valign="top">
114
+ <div class="content"
115
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
116
+
117
+ <div style="text-align:center">
118
+ <!--
119
+ <a href="http://www.tiledesk.com"
120
+ style="color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word" target="_blank">
121
+ <img src="https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png" class="CToWUd">
122
+ </a>
123
+ -->
124
+ </div>
125
+ <table class="main" width="100%" cellpadding="0" cellspacing="0"
126
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
127
+ bgcolor="#fff">
128
+
129
+ <!-- <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
130
+
131
+ </tr> -->
132
+
133
+ <tr
134
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
135
+ <td class="content-wrap"
136
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
137
+ valign="top">
138
+ <table width="100%" cellpadding="0" cellspacing="0"
139
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
140
+
141
+
142
+ <tr
143
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
144
+
145
+ <td class="content-block"
146
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
147
+ valign="top">
148
+ <h2
149
+ style="text-align: center; letter-spacing: 1px; font-weight: 400; line-height:24px ">
150
+ {{currentUserFirstname}} {{currentUserLastname}} has invited you to the
151
+ Tiledesk project
152
+ <strong> {{projectName}}</strong>
153
+ </h2>
154
+
155
+ <!-- <br> <br><strong
156
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">{{to}},</strong> -->
157
+
158
+ <br> <br>
159
+ I invited you to take on the role of {{invitedUserRole}} of the Tiledesk
160
+ <strong>
161
+ {{projectName}}</strong> project
162
+
163
+
164
+ <div style="text-align: center;">
165
+ <br><br>
166
+
167
+ <a href="{{baseScope.baseUrl}}/#/handle-invitation/{{pendinginvitationid}}/{{projectName}}/{{currentUserFirstname}}/{{currentUserLastname}}"
168
+ style=" background-color: #ff8574 !important; border: none; color: white; padding: 12px 30px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer;">
169
+ GO TO THE PROJECT
170
+ </a>
171
+ </div>
172
+
173
+ <br><br> The Tiledesk Team
174
+ </td>
175
+ </tr>
176
+
177
+ <tr
178
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
179
+ <td class="content-block"
180
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
181
+ valign="top">
182
+ </td>
183
+ </tr>
184
+ </table>
185
+ </td>
186
+ </tr>
187
+
188
+ <tr>
189
+ <td>
190
+ <hr style="width:94%;height:1px;border:none;background-color: #cacaca;">
191
+
192
+ <div style="display: flex; padding: 20px 18px; color: #888888; align-items: center;">
193
+ <span>Powered by </span>
194
+ <span style="display: flex;"><img
195
+ src="https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png"
196
+ width="15" height="15" style="margin-left: 6px; margin-top: 2px;" /></span>
197
+ <span style="font-weight: bold; margin-left: 2px;">Tiledesk</span>
198
+ </div>
199
+
200
+ </td>
201
+ </tr>
202
+
203
+ </table>
204
+ <div class="footer"
205
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
206
+ <table width="100%"
207
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
208
+ <tr
209
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
210
+ <td class="aligncenter content-block"
211
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;"
212
+ align="center" valign="top">
213
+ <span><a href="http://www.tiledesk.com"
214
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">
215
+ Tiledesk.com </a></span>
216
+ <br><span><a href="%unsubscribe_url%"
217
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">Unsubscribe</a></span>
218
+ </td>
219
+ </tr>
220
+ </table>
221
+ </div>
222
+ </div>
223
+ </td>
224
+ <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
225
+ valign="top"></td>
226
+ </tr>
227
+ </table>
228
+ </body>
229
+
230
+ </html>
@@ -84,6 +84,52 @@ describe('RequestRoute', () => {
84
84
  });
85
85
 
86
86
 
87
+ it('createSimpleAndCloseForDuration', function (done) {
88
+ // this.timeout(10000);
89
+
90
+ var email = "test-request-create-" + Date.now() + "@email.com";
91
+ var pwd = "pwd";
92
+
93
+ userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
94
+ projectService.create("request-create", savedUser._id, { email: { template: { assignedRequest: "123" } } }).then(function (savedProject) {
95
+
96
+ chai.request(server)
97
+ .post('/' + savedProject._id + '/requests/')
98
+ .auth(email, pwd)
99
+ .set('content-type', 'application/json')
100
+ .send({ "first_text": "first_text" })
101
+ .end(function (err, res) {
102
+ //console.log("res", res);
103
+ //console.log("res.body", res.body);
104
+ res.should.have.status(200);
105
+ res.body.should.be.a('object');
106
+
107
+ //console.log("res.body: ", res.body)
108
+
109
+ setTimeout(() => {
110
+
111
+ chai.request(server)
112
+ .put('/' + savedProject._id + '/requests/' + res.body.request_id + '/close')
113
+ .auth(email, pwd)
114
+ .send()
115
+ .end((err, res) => {
116
+
117
+ if (err) { console.error("err: ", err) };
118
+ console.log("request duration: ", res.body.duration)
119
+
120
+ res.body.should.have.property('duration');
121
+ res.body.duration.should.be.above(2000);
122
+
123
+ done();
124
+ })
125
+
126
+ }, 2000)
127
+
128
+ });
129
+ });
130
+ });
131
+ }).timeout(5000);
132
+
87
133
 
88
134
 
89
135