@tiledesk/tiledesk-server 2.8.0 → 2.8.1

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
@@ -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