@jsreport/jsreport-core 3.4.0 → 3.5.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/README.md +19 -1
- package/lib/main/blobStorage/blobStorage.js +9 -0
- package/lib/main/optionsSchema.js +16 -1
- package/lib/main/profiler.js +166 -75
- package/lib/main/reporter.js +90 -82
- package/lib/main/request.js +21 -0
- package/lib/main/schemaValidator.js +30 -0
- package/lib/main/settings.js +1 -2
- package/lib/main/store/collection.js +10 -8
- package/lib/main/store/documentStore.js +19 -0
- package/lib/main/store/setupValidateId.js +7 -1
- package/lib/main/store/setupValidateShortid.js +4 -0
- package/lib/shared/normalizeMetaFromLogs.js +1 -1
- package/lib/shared/reporter.js +16 -0
- package/lib/worker/render/executeEngine.js +30 -7
- package/lib/worker/render/profiler.js +14 -12
- package/lib/worker/render/render.js +0 -4
- package/lib/worker/sandbox/runInSandbox.js +2 -5
- package/lib/worker/sandbox/safeSandbox.js +8 -30
- package/lib/worker/workerHandler.js +1 -0
- package/package.json +7 -7
- package/lib/main/monitoring.js +0 -92
package/lib/main/reporter.js
CHANGED
|
@@ -25,13 +25,13 @@ const setupValidateShortid = require('./store/setupValidateShortid')
|
|
|
25
25
|
const documentStoreActions = require('./store/mainActions')
|
|
26
26
|
const blobStorageActions = require('./blobStorage/mainActions')
|
|
27
27
|
const Reporter = require('../shared/reporter')
|
|
28
|
-
const Request = require('
|
|
28
|
+
const Request = require('./request')
|
|
29
29
|
const generateRequestId = require('../shared/generateRequestId')
|
|
30
30
|
const Profiler = require('./profiler')
|
|
31
|
-
const Monitoring = require('./monitoring')
|
|
32
31
|
const migrateXlsxTemplatesToAssets = require('./migration/xlsxTemplatesToAssets')
|
|
33
32
|
const migrateResourcesToAssets = require('./migration/resourcesToAssets')
|
|
34
33
|
const semver = require('semver')
|
|
34
|
+
let reportCounter = 0
|
|
35
35
|
|
|
36
36
|
class MainReporter extends Reporter {
|
|
37
37
|
constructor (options, defaults) {
|
|
@@ -69,6 +69,7 @@ class MainReporter extends Reporter {
|
|
|
69
69
|
|
|
70
70
|
this.logger = createLogger()
|
|
71
71
|
this.beforeMainActionListeners = this.createListenerCollection('beforeMainAction')
|
|
72
|
+
this.beforeRenderWorkerAllocatedListeners = this.createListenerCollection('beforeRenderWorkerAllocated')
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
discover () {
|
|
@@ -165,9 +166,11 @@ class MainReporter extends Reporter {
|
|
|
165
166
|
this._initializing = true
|
|
166
167
|
|
|
167
168
|
if (this.compilation) {
|
|
168
|
-
this.compilation.resource('
|
|
169
|
-
this.compilation.resource('
|
|
170
|
-
this.compilation.resource('
|
|
169
|
+
this.compilation.resource('vm2-events.js', require.resolve('vm2/lib/events.js'))
|
|
170
|
+
this.compilation.resource('vm2-resolver-compat.js', require.resolve('vm2/lib/resolver-compat.js'))
|
|
171
|
+
this.compilation.resource('vm2-resolver.js', require.resolve('vm2/lib/resolver.js'))
|
|
172
|
+
this.compilation.resource('vm2-setup-node-sandbox.js', require.resolve('vm2/lib/setup-node-sandbox.js'))
|
|
173
|
+
this.compilation.resource('vm2-setup-sandbox.js', require.resolve('vm2/lib/setup-sandbox.js'))
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
try {
|
|
@@ -180,7 +183,6 @@ class MainReporter extends Reporter {
|
|
|
180
183
|
blobStorageActions(this)
|
|
181
184
|
Templates(this)
|
|
182
185
|
Profiler(this)
|
|
183
|
-
Monitoring(this)
|
|
184
186
|
|
|
185
187
|
this.folders = Object.assign(this.folders, Folders(this))
|
|
186
188
|
|
|
@@ -267,8 +269,6 @@ class MainReporter extends Reporter {
|
|
|
267
269
|
name: 'none'
|
|
268
270
|
})
|
|
269
271
|
|
|
270
|
-
this.monitoring.init()
|
|
271
|
-
|
|
272
272
|
this.logger.info('reporter initialized')
|
|
273
273
|
this._initialized = true
|
|
274
274
|
this._initExecution.resolve()
|
|
@@ -306,6 +306,34 @@ class MainReporter extends Reporter {
|
|
|
306
306
|
await validateReservedName(this, c, doc)
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
async _handleRenderError (req, res, err) {
|
|
310
|
+
if (err.code === 'WORKER_TIMEOUT') {
|
|
311
|
+
err.message = 'Report timeout'
|
|
312
|
+
if (req.context.profiling?.lastOperation != null && req.context.profiling?.entity != null) {
|
|
313
|
+
err.message += `. Last profiler operation: (${req.context.profiling.lastOperation.subtype}) ${req.context.profiling.lastOperation.name}`
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (req.context.http != null) {
|
|
317
|
+
const profileUrl = `${req.context.http.baseUrl}/studio/profiles/${req.context.profiling.entity._id}`
|
|
318
|
+
err.message += `. You can inspect and find more details here: ${profileUrl}`
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
err.weak = true
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (err.code === 'WORKER_ABORTED') {
|
|
325
|
+
err.message = 'Report cancelled'
|
|
326
|
+
err.weak = true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!err.logged) {
|
|
330
|
+
const logFn = err.weak ? this.logger.warn : this.logger.error
|
|
331
|
+
logFn(`Report render failed: ${err.message}${err.stack != null ? ' ' + err.stack : ''}`, req)
|
|
332
|
+
}
|
|
333
|
+
await this.renderErrorListeners.fire(req, res, err)
|
|
334
|
+
throw err
|
|
335
|
+
}
|
|
336
|
+
|
|
309
337
|
/**
|
|
310
338
|
* Main method for invoking rendering
|
|
311
339
|
* render({ template: { content: 'foo', engine: 'none', recipe: 'html' }, data: { foo: 'hello' } })
|
|
@@ -315,63 +343,55 @@ class MainReporter extends Reporter {
|
|
|
315
343
|
*
|
|
316
344
|
* @public
|
|
317
345
|
*/
|
|
318
|
-
async render (req,
|
|
346
|
+
async render (req, options = {}) {
|
|
319
347
|
if (!this._initialized) {
|
|
320
348
|
throw new Error('Not initialized, you need to call jsreport.init().then before rendering')
|
|
321
349
|
}
|
|
322
350
|
|
|
323
|
-
let options = {}
|
|
324
|
-
if (parentReq && !parentReq.__isJsreportRequest__) {
|
|
325
|
-
options = parentReq
|
|
326
|
-
parentReq = null
|
|
327
|
-
}
|
|
328
|
-
|
|
329
351
|
req = Object.assign({}, req)
|
|
330
352
|
req.context = Object.assign({}, req.context)
|
|
331
353
|
req.context.rootId = req.context.rootId || generateRequestId()
|
|
332
354
|
req.context.id = req.context.rootId
|
|
355
|
+
req.context.reportCounter = ++reportCounter
|
|
356
|
+
req.context.startTimestamp = new Date().getTime()
|
|
333
357
|
|
|
334
|
-
|
|
335
|
-
timeout: this.options.reportTimeout
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
let keepWorker
|
|
358
|
+
let worker
|
|
339
359
|
let workerAborted
|
|
360
|
+
let dontCloseProcessing
|
|
361
|
+
const res = { meta: {} }
|
|
362
|
+
try {
|
|
363
|
+
await this.beforeRenderWorkerAllocatedListeners.fire(req)
|
|
340
364
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
workerAborted = true
|
|
344
|
-
worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
|
|
365
|
+
worker = await this._workersManager.allocate(req, {
|
|
366
|
+
timeout: this.getReportTimeout(req)
|
|
345
367
|
})
|
|
346
|
-
}
|
|
347
368
|
|
|
348
|
-
|
|
349
|
-
|
|
369
|
+
if (options.abortEmitter) {
|
|
370
|
+
options.abortEmitter.once('abort', () => {
|
|
371
|
+
if (workerAborted) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
workerAborted = true
|
|
375
|
+
worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
350
379
|
if (workerAborted) {
|
|
351
380
|
throw this.createError('Request aborted by client')
|
|
352
381
|
}
|
|
353
382
|
|
|
354
|
-
let isDataStoredInWorker = false
|
|
355
|
-
|
|
356
383
|
if (req.rawContent) {
|
|
357
|
-
isDataStoredInWorker = true
|
|
358
384
|
const result = await worker.execute({
|
|
359
385
|
actionName: 'parse',
|
|
360
386
|
req,
|
|
361
387
|
data: {}
|
|
362
388
|
}, {
|
|
363
|
-
timeout: this.
|
|
389
|
+
timeout: this.getReportTimeout(req)
|
|
364
390
|
})
|
|
365
391
|
req = result
|
|
366
392
|
}
|
|
367
393
|
|
|
368
|
-
req = Request(req
|
|
369
|
-
|
|
370
|
-
if (isDataStoredInWorker) {
|
|
371
|
-
// we unset this because we want the Request() call in worker to evaluate the data
|
|
372
|
-
// and determine if the original was empty or not
|
|
373
|
-
delete req.context.originalInputDataIsEmpty
|
|
374
|
-
}
|
|
394
|
+
req = Request(req)
|
|
375
395
|
|
|
376
396
|
// TODO: we will probably validate in the thread
|
|
377
397
|
if (this.entityTypeValidator.getSchema('TemplateType') != null) {
|
|
@@ -384,29 +404,40 @@ class MainReporter extends Reporter {
|
|
|
384
404
|
}
|
|
385
405
|
}
|
|
386
406
|
|
|
387
|
-
|
|
407
|
+
const reportTimeout = this.getReportTimeout(req)
|
|
388
408
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
) {
|
|
394
|
-
reportTimeout = req.options.timeout
|
|
409
|
+
await this.beforeRenderListeners.fire(req, res, { worker })
|
|
410
|
+
|
|
411
|
+
if (workerAborted) {
|
|
412
|
+
throw this.createError('Request aborted by client')
|
|
395
413
|
}
|
|
396
414
|
|
|
397
|
-
|
|
415
|
+
if (req.context.clientNotification) {
|
|
416
|
+
process.nextTick(async () => {
|
|
417
|
+
try {
|
|
418
|
+
const responseResult = await this.executeWorkerAction('render', {}, {
|
|
419
|
+
timeout: reportTimeout + this.options.reportTimeoutMargin,
|
|
420
|
+
worker
|
|
421
|
+
}, req)
|
|
422
|
+
|
|
423
|
+
Object.assign(res, responseResult)
|
|
424
|
+
await this.afterRenderListeners.fire(req, res)
|
|
425
|
+
} catch (err) {
|
|
426
|
+
await this._handleRenderError(req, res, err).catch((e) => {})
|
|
427
|
+
} finally {
|
|
428
|
+
if (!workerAborted) {
|
|
429
|
+
await worker.release(req)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
})
|
|
398
433
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
keepWorker = true
|
|
407
|
-
res.stream = Readable.from(res.content)
|
|
408
|
-
await this.afterRenderListeners.fire(req, res)
|
|
409
|
-
return res
|
|
434
|
+
dontCloseProcessing = true
|
|
435
|
+
const r = {
|
|
436
|
+
...req.context.clientNotification,
|
|
437
|
+
stream: Readable.from(req.context.clientNotification.content)
|
|
438
|
+
}
|
|
439
|
+
delete req.context.clientNotification
|
|
440
|
+
return r
|
|
410
441
|
}
|
|
411
442
|
|
|
412
443
|
if (workerAborted) {
|
|
@@ -423,33 +454,10 @@ class MainReporter extends Reporter {
|
|
|
423
454
|
res.stream = Readable.from(res.content)
|
|
424
455
|
return res
|
|
425
456
|
} catch (err) {
|
|
426
|
-
|
|
427
|
-
err.message = 'Report timeout'
|
|
428
|
-
if (req.context.profiling?.lastOperation != null && req.context.profiling?.entity != null) {
|
|
429
|
-
err.message += `. Last profiler operation: (${req.context.profiling.lastOperation.subtype}) ${req.context.profiling.lastOperation.name}`
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (req.context.http != null) {
|
|
433
|
-
const profileUrl = `${req.context.http.baseUrl}/studio/profiles/${req.context.profiling.entity._id}`
|
|
434
|
-
err.message += `. You can inspect and find more details here: ${profileUrl}`
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
err.weak = true
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (err.code === 'WORKER_ABORTED') {
|
|
441
|
-
err.message = 'Report cancelled'
|
|
442
|
-
err.weak = true
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (!err.logged) {
|
|
446
|
-
const logFn = err.weak ? this.logger.warn : this.logger.error
|
|
447
|
-
logFn(`Report render failed: ${err.message}${err.stack != null ? ' ' + err.stack : ''}`, req)
|
|
448
|
-
}
|
|
449
|
-
await this.renderErrorListeners.fire(req, res, err)
|
|
457
|
+
await this._handleRenderError(req, res, err)
|
|
450
458
|
throw err
|
|
451
459
|
} finally {
|
|
452
|
-
if (!workerAborted && !
|
|
460
|
+
if (worker && !workerAborted && !dontCloseProcessing) {
|
|
453
461
|
await worker.release(req)
|
|
454
462
|
}
|
|
455
463
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const extend = require('node.extend.without.arrays')
|
|
2
|
+
|
|
3
|
+
module.exports = (obj) => {
|
|
4
|
+
const request = Object.create({}, {
|
|
5
|
+
__isJsreportRequest__: {
|
|
6
|
+
value: true,
|
|
7
|
+
writable: false,
|
|
8
|
+
configurable: false,
|
|
9
|
+
enumerable: false
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
request.template = extend(true, {}, obj.template)
|
|
14
|
+
|
|
15
|
+
request.options = extend(true, {}, request.options, obj.options)
|
|
16
|
+
request.context = extend(true, {}, request.context, obj.context)
|
|
17
|
+
request.context.shared = extend(true, {}, request.context.shared)
|
|
18
|
+
request.data = obj.data
|
|
19
|
+
|
|
20
|
+
return request
|
|
21
|
+
}
|
|
@@ -3,6 +3,7 @@ const set = require('lodash.set')
|
|
|
3
3
|
const hasOwn = require('has-own-deep')
|
|
4
4
|
const unsetValue = require('unset-value')
|
|
5
5
|
const ms = require('ms')
|
|
6
|
+
const bytes = require('bytes')
|
|
6
7
|
const Ajv = require('ajv')
|
|
7
8
|
|
|
8
9
|
const validatorCollection = new WeakMap()
|
|
@@ -126,6 +127,35 @@ class SchemaValidator {
|
|
|
126
127
|
}
|
|
127
128
|
})
|
|
128
129
|
|
|
130
|
+
validator.addKeyword('$jsreport-acceptsSize', {
|
|
131
|
+
modifying: true,
|
|
132
|
+
compile: (sch) => {
|
|
133
|
+
if (sch !== true) {
|
|
134
|
+
return () => true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return (data, dataPath, parentData, parentDataProperty) => {
|
|
138
|
+
if (typeof data !== 'string' && typeof data !== 'number') {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (typeof data === 'number') {
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const newData = bytes(data)
|
|
147
|
+
|
|
148
|
+
if (newData == null) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
parentData[parentDataProperty] = newData
|
|
153
|
+
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
129
159
|
let rootValidate
|
|
130
160
|
|
|
131
161
|
if (options.rootSchema != null) {
|
package/lib/main/settings.js
CHANGED
|
@@ -25,7 +25,7 @@ Settings.prototype.get = function (key) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
Settings.prototype.findValue = async function (key, req) {
|
|
28
|
-
const res = await this.documentStore.collection('settings').find({ key: key }, req)
|
|
28
|
+
const res = await this.documentStore.collection('settings').find({ key: key }, localReqWithoutAuthorization(req))
|
|
29
29
|
if (res.length !== 1) {
|
|
30
30
|
return null
|
|
31
31
|
}
|
|
@@ -49,7 +49,6 @@ Settings.prototype.addOrSet = async function (key, avalue, req) {
|
|
|
49
49
|
const value = typeof avalue !== 'string' ? JSON.stringify(avalue) : avalue
|
|
50
50
|
|
|
51
51
|
const updateCount = await this.documentStore.collection('settings').update({ key }, { $set: { key: key, value: value } }, localReqWithoutAuthorization(req))
|
|
52
|
-
|
|
53
52
|
if (updateCount === 0) {
|
|
54
53
|
await this.documentStore.collection('settings').insert({ key: key, value: value }, localReqWithoutAuthorization(req))
|
|
55
54
|
return 1
|
|
@@ -86,16 +86,18 @@ module.exports = (entitySet, provider, model, validator, encryption, transaction
|
|
|
86
86
|
validateEntityName(data.name)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
if (req == null || req.context.skipValidationFor !== data) {
|
|
90
|
+
const entityType = model.entitySets[entitySet] ? model.entitySets[entitySet].normalizedEntityTypeName : null
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
if (entityType != null && validator.getSchema(entityType) != null) {
|
|
93
|
+
const validationResult = validator.validate(entityType, data)
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
if (!validationResult.valid) {
|
|
96
|
+
throw createError(`Error when trying to insert into "${entitySet}" collection. input contain values that does not match the schema. ${validationResult.fullErrorMessage}`, {
|
|
97
|
+
weak: true,
|
|
98
|
+
statusCode: 400
|
|
99
|
+
})
|
|
100
|
+
}
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -372,6 +372,10 @@ const DocumentStore = (options, validator, encryption) => {
|
|
|
372
372
|
},
|
|
373
373
|
|
|
374
374
|
async beginTransaction (req) {
|
|
375
|
+
if (this.options.store?.transactions?.enabled === false) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
375
379
|
if (req.context.storeTransaction && transactions.has(req.context.storeTransaction)) {
|
|
376
380
|
throw new Error('Can not call store.beginTransaction when an active transaction already exists, make sure you are not calling store.beginTransaction more than once')
|
|
377
381
|
}
|
|
@@ -386,6 +390,10 @@ const DocumentStore = (options, validator, encryption) => {
|
|
|
386
390
|
},
|
|
387
391
|
|
|
388
392
|
async commitTransaction (req) {
|
|
393
|
+
if (this.options.store?.transactions?.enabled === false) {
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
|
|
389
397
|
const tranId = req.context.storeTransaction
|
|
390
398
|
const tran = transactions.get(tranId)
|
|
391
399
|
|
|
@@ -400,6 +408,10 @@ const DocumentStore = (options, validator, encryption) => {
|
|
|
400
408
|
},
|
|
401
409
|
|
|
402
410
|
async rollbackTransaction (req) {
|
|
411
|
+
if (this.options.store?.transactions?.enabled === false) {
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
403
415
|
const tranId = req.context.storeTransaction
|
|
404
416
|
const tran = transactions.get(tranId)
|
|
405
417
|
|
|
@@ -411,6 +423,13 @@ const DocumentStore = (options, validator, encryption) => {
|
|
|
411
423
|
|
|
412
424
|
transactions.delete(tranId)
|
|
413
425
|
delete req.context.storeTransaction
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
generateId () {
|
|
429
|
+
if (this.provider.generateId) {
|
|
430
|
+
return this.provider.generateId()
|
|
431
|
+
}
|
|
432
|
+
return uuidv4()
|
|
414
433
|
}
|
|
415
434
|
}
|
|
416
435
|
|
|
@@ -3,10 +3,16 @@ module.exports = (reporter) => {
|
|
|
3
3
|
reporter.initializeListeners.add('core-validate-id', () => {
|
|
4
4
|
for (const c of Object.keys(reporter.documentStore.collections)) {
|
|
5
5
|
reporter.documentStore.collection(c).beforeInsertListeners.add('validate-id', (doc, req) => {
|
|
6
|
-
|
|
6
|
+
if (req == null || req.context.skipValidationFor !== doc) {
|
|
7
|
+
return validateIdForStoreChange(reporter, c, doc._id, undefined, req)
|
|
8
|
+
}
|
|
7
9
|
})
|
|
8
10
|
|
|
9
11
|
reporter.documentStore.collection(c).beforeUpdateListeners.add('validate-id', async (q, update, opts, req) => {
|
|
12
|
+
if (req != null && req.context.skipValidationFor === update) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
if (update.$set && opts && opts.upsert === true) {
|
|
11
17
|
await validateIdForStoreChange(reporter, c, update.$set._id, undefined, req)
|
|
12
18
|
}
|
|
@@ -31,6 +31,10 @@ module.exports = (reporter) => {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function validateShortid (reporter, collectionName, doc, originalIdValue, req) {
|
|
34
|
+
if (req != null && req.context.skipValidationFor === doc) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
const shortid = doc.shortid
|
|
35
39
|
|
|
36
40
|
if (!shortid) {
|
|
@@ -13,7 +13,7 @@ module.exports = (level, msg, meta) => {
|
|
|
13
13
|
|
|
14
14
|
// TODO adding cancel looks bad, its before script is adding req.cancel()
|
|
15
15
|
// excluding non relevant properties for the log
|
|
16
|
-
const newMeta = Object.assign({}, omit(meta, ['template', 'options', 'data', 'context', 'timestamp', 'cancel']))
|
|
16
|
+
const newMeta = Object.assign({}, omit(meta, ['rawContent', 'template', 'options', 'data', 'context', 'timestamp', 'cancel']))
|
|
17
17
|
|
|
18
18
|
if (newMeta.rootId == null && meta.context.rootId != null) {
|
|
19
19
|
newMeta.rootId = meta.context.rootId
|
package/lib/shared/reporter.js
CHANGED
|
@@ -51,6 +51,22 @@ class Reporter extends EventEmitter {
|
|
|
51
51
|
return generateRequestId()
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @public Ensures that we get the proper report timeout in case when custom timeout per request was enabled
|
|
56
|
+
*/
|
|
57
|
+
getReportTimeout (req) {
|
|
58
|
+
const elapsedTime = req.context.startTimestamp ? (new Date().getTime() - req.context.startTimestamp) : 0
|
|
59
|
+
if (
|
|
60
|
+
this.options.enableRequestReportTimeout &&
|
|
61
|
+
req.options != null &&
|
|
62
|
+
req.options.timeout != null
|
|
63
|
+
) {
|
|
64
|
+
return Math.max(0, req.options.timeout - elapsedTime)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Math.max(0, this.options.reportTimeout - elapsedTime)
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
/**
|
|
55
71
|
* Ensures that the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory) exists by doing a mkdir call
|
|
56
72
|
*
|
|
@@ -14,6 +14,7 @@ module.exports = (reporter) => {
|
|
|
14
14
|
reporter.templatingEngines = { cache }
|
|
15
15
|
|
|
16
16
|
const executionFnParsedParamsMap = new Map()
|
|
17
|
+
const executionAsyncResultsMap = new Map()
|
|
17
18
|
|
|
18
19
|
const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
|
|
19
20
|
const engineImpl = reporter.extensionsManager.engines.find((e) => e.name === engine)
|
|
@@ -28,19 +29,23 @@ module.exports = (reporter) => {
|
|
|
28
29
|
executionFnParsedParamsMap.set(req.context.id, new Map())
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
const executionId = nanoid(7)
|
|
33
|
+
|
|
31
34
|
try {
|
|
32
35
|
const res = await executeEngine({
|
|
33
36
|
engine: engineImpl,
|
|
34
37
|
content,
|
|
35
38
|
helpers,
|
|
36
39
|
data
|
|
37
|
-
}, { handleErrors: false, entity, entitySet }, req)
|
|
40
|
+
}, { executionId, handleErrors: false, entity, entitySet }, req)
|
|
38
41
|
|
|
39
42
|
return res.content
|
|
40
43
|
} finally {
|
|
41
44
|
if (mainCall) {
|
|
42
45
|
executionFnParsedParamsMap.delete(req.context.id)
|
|
43
46
|
}
|
|
47
|
+
|
|
48
|
+
executionAsyncResultsMap.delete(executionId)
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -54,6 +59,12 @@ module.exports = (reporter) => {
|
|
|
54
59
|
proxy.templatingEngines = {
|
|
55
60
|
evaluate: async (executionInfo, entityInfo) => {
|
|
56
61
|
return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
|
|
62
|
+
},
|
|
63
|
+
waitForAsyncHelpers: async () => {
|
|
64
|
+
if (context.__executionId != null && executionAsyncResultsMap.has(context.__executionId)) {
|
|
65
|
+
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
66
|
+
return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
|
|
67
|
+
}
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
})
|
|
@@ -64,6 +75,8 @@ module.exports = (reporter) => {
|
|
|
64
75
|
req.data.__rootDirectory = reporter.options.rootDirectory
|
|
65
76
|
req.data.__parentModuleDirectory = reporter.options.parentModuleDirectory
|
|
66
77
|
|
|
78
|
+
const executionId = nanoid(7)
|
|
79
|
+
|
|
67
80
|
try {
|
|
68
81
|
return await executeEngine({
|
|
69
82
|
engine,
|
|
@@ -71,16 +84,18 @@ module.exports = (reporter) => {
|
|
|
71
84
|
helpers: req.template.helpers,
|
|
72
85
|
data: req.data
|
|
73
86
|
}, {
|
|
87
|
+
executionId,
|
|
74
88
|
handleErrors: true,
|
|
75
89
|
entity: req.template,
|
|
76
90
|
entitySet: 'templates'
|
|
77
91
|
}, req)
|
|
78
92
|
} finally {
|
|
79
93
|
executionFnParsedParamsMap.delete(req.context.id)
|
|
94
|
+
executionAsyncResultsMap.delete(executionId)
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
async function executeEngine ({ engine, content, helpers, data }, { handleErrors, entity, entitySet }, req) {
|
|
98
|
+
async function executeEngine ({ engine, content, helpers, data }, { executionId, handleErrors, entity, entitySet }, req) {
|
|
84
99
|
let entityPath
|
|
85
100
|
|
|
86
101
|
if (entity._id) {
|
|
@@ -104,9 +119,14 @@ module.exports = (reporter) => {
|
|
|
104
119
|
const joinedHelpers = systemHelpersStr + '\n' + (helpers || '')
|
|
105
120
|
const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${joinedHelpers}`
|
|
106
121
|
|
|
107
|
-
const executionFn = async ({ require, console, topLevelFunctions }) => {
|
|
122
|
+
const executionFn = async ({ require, console, topLevelFunctions, context }) => {
|
|
108
123
|
const asyncResultMap = new Map()
|
|
109
|
-
|
|
124
|
+
|
|
125
|
+
context.__executionId = executionId
|
|
126
|
+
|
|
127
|
+
executionAsyncResultsMap.set(executionId, asyncResultMap)
|
|
128
|
+
executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
|
|
129
|
+
|
|
110
130
|
const key = `template:${content}:${engine.name}`
|
|
111
131
|
|
|
112
132
|
if (!cache.has(key)) {
|
|
@@ -143,16 +163,19 @@ module.exports = (reporter) => {
|
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
return {
|
|
146
|
-
|
|
166
|
+
// handlebars escapes single brackets before execution to prevent errors on {#asset}
|
|
167
|
+
// we need to unescape them later here, because at the moment the engine.execute finishes
|
|
168
|
+
// the async helpers aren't executed yet
|
|
169
|
+
content: engine.unescape ? engine.unescape(contentResult) : contentResult
|
|
147
170
|
}
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
// executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
|
|
151
174
|
// we store there for the particular request and component a promise and only the first component gets compiled
|
|
152
175
|
if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
|
|
153
|
-
const { require, console, topLevelFunctions } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
|
|
176
|
+
const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
|
|
154
177
|
|
|
155
|
-
return executionFn({ require, console, topLevelFunctions })
|
|
178
|
+
return executionFn({ require, console, topLevelFunctions, context })
|
|
156
179
|
} else {
|
|
157
180
|
const awaiter = {}
|
|
158
181
|
awaiter.promise = new Promise((resolve) => {
|
|
@@ -57,9 +57,9 @@ class Profiler {
|
|
|
57
57
|
|
|
58
58
|
if (m.type !== 'log') {
|
|
59
59
|
req.context.profiling.lastEventId = m.id
|
|
60
|
+
m.operationId = m.operationId || generateRequestId()
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
m.operationId = m.operationId || generateRequestId()
|
|
63
63
|
if (m.previousOperationId == null && req.context.profiling.lastOperationId) {
|
|
64
64
|
m.previousOperationId = req.context.profiling.lastOperationId
|
|
65
65
|
}
|
|
@@ -72,15 +72,21 @@ class Profiler {
|
|
|
72
72
|
let content = res.content
|
|
73
73
|
|
|
74
74
|
if (content != null) {
|
|
75
|
-
if (
|
|
75
|
+
if (content.length > this.reporter.options.profiler.maxResponseSize) {
|
|
76
76
|
content = {
|
|
77
|
-
|
|
78
|
-
encoding: 'base64'
|
|
77
|
+
tooLarge: true
|
|
79
78
|
}
|
|
80
79
|
} else {
|
|
81
|
-
content
|
|
82
|
-
content
|
|
83
|
-
|
|
80
|
+
if (isbinaryfile(content)) {
|
|
81
|
+
content = {
|
|
82
|
+
content: res.content.toString('base64'),
|
|
83
|
+
encoding: 'base64'
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
content = {
|
|
87
|
+
content: createPatch('res', req.context.profiling.resLastVal ? req.context.profiling.resLastVal.toString() : '', res.content.toString(), 0),
|
|
88
|
+
encoding: 'diff'
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
}
|
|
@@ -93,7 +99,7 @@ class Profiler {
|
|
|
93
99
|
|
|
94
100
|
m.req = { diff: createPatch('req', req.context.profiling.reqLastVal || '', stringifiedReq, 0) }
|
|
95
101
|
|
|
96
|
-
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content)) ? null : res.content.toString()
|
|
102
|
+
req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content) || content.tooLarge) ? null : res.content.toString()
|
|
97
103
|
req.context.profiling.resMetaLastVal = stringifiedResMeta
|
|
98
104
|
req.context.profiling.reqLastVal = stringifiedReq
|
|
99
105
|
}
|
|
@@ -129,10 +135,6 @@ class Profiler {
|
|
|
129
135
|
previousOperationId: parentReq ? parentReq.context.profiling.lastOperationId : null
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
if (!req.context.isChildRequest) {
|
|
133
|
-
profilerEvent.profileId = req.context.profiling.entity._id
|
|
134
|
-
}
|
|
135
|
-
|
|
136
138
|
return this.emit(profilerEvent, req, res)
|
|
137
139
|
}
|
|
138
140
|
|