@jsreport/jsreport-core 3.6.1 → 3.8.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 CHANGED
@@ -282,6 +282,24 @@ jsreport.documentStore.collection('templates')
282
282
 
283
283
  ## Changelog
284
284
 
285
+ ### 3.8.0
286
+
287
+ - make "config.json" a reserved name for entities
288
+
289
+ ### 3.7.0
290
+
291
+ - add support for multiple source entities when copying and moving
292
+ - fix some issues with blobs and profiles
293
+ - format user logs differently on stdout
294
+ - fix logging of req as http.IncomingRequest
295
+ - fix profile compatibility with jsreport container based execution
296
+ - fix memory leak in profiling
297
+ - add support for logs of main reporter to show in profile
298
+
299
+ ### 3.6.1
300
+
301
+ - update @jsreport/advanced-workers to fix bug with cloning req.data when `trustUserCode` is true
302
+
285
303
  ### 3.6.0
286
304
 
287
305
  - improve handling of worker exit
package/index.js CHANGED
@@ -25,5 +25,5 @@ module.exports.createDefaultLoggerFormat = createDefaultLoggerFormat
25
25
  module.exports.tests = {
26
26
  documentStore: () => require('./test/store/common.js'),
27
27
  blobStorage: () => require('./test/blobStorage/common.js'),
28
- listeners: () => require('./test/extensions/validExtensions/listeners/jsreport.config')
28
+ listeners: () => require('./test/extensions/validExtensions/listeners/jsreport.dontdiscover.config')
29
29
  }
@@ -1,10 +1,11 @@
1
1
  const { MESSAGE } = require('triple-beam')
2
+ const colors = require('@colors/colors/safe')
2
3
  const winston = require('winston')
3
4
 
4
5
  module.exports = (options = {}) => {
5
6
  return winston.format((info) => {
6
7
  const { level, message, ...meta } = info
7
- info[MESSAGE] = `${options.timestamp === true ? `${new Date().toISOString()} - ` : ''}${level}: ${message}`
8
+ info[MESSAGE] = `${options.timestamp === true ? `${new Date().toISOString()} - ` : ''}${level}: ${info.userLevel === true ? colors.cyan(message) : message}`
8
9
 
9
10
  const metaKeys = Object.keys(meta)
10
11
 
@@ -12,6 +13,10 @@ module.exports = (options = {}) => {
12
13
  info[MESSAGE] += ` ${metaKeys.map((k) => `${k}=${meta[k]}`).join(', ')}`
13
14
  }
14
15
 
16
+ if (info.userLevel === true) {
17
+ info.level = colors.cyan(info.level)
18
+ }
19
+
15
20
  return info
16
21
  })
17
22
  }
@@ -0,0 +1,19 @@
1
+ const winston = require('winston')
2
+ const normalizeMetaFromLogs = require('../shared/normalizeMetaFromLogs')
3
+
4
+ module.exports = () => {
5
+ return winston.format((info) => {
6
+ const { level, message, ...meta } = info
7
+ const newMeta = normalizeMetaFromLogs(level, message, meta)
8
+
9
+ if (newMeta != null) {
10
+ return {
11
+ level,
12
+ message,
13
+ ...newMeta
14
+ }
15
+ }
16
+
17
+ return info
18
+ })
19
+ }
@@ -31,27 +31,8 @@ async function throwIfEntityIsNotFolder (reporter, targetShortid, req) {
31
31
  }
32
32
 
33
33
  module.exports = (reporter) => async ({ source, target, shouldCopy, shouldReplace }, req) => {
34
- const sourceCol = reporter.documentStore.collection(source.entitySet)
35
-
36
- if (!sourceCol) {
37
- throw reporter.createError(`Invalid entity set "${source.entitySet}" for source`, {
38
- statusCode: 400
39
- })
40
- }
41
-
42
- const sourceEntity = await sourceCol.findOne({ _id: source.id }, req)
43
-
44
- if (!sourceEntity) {
45
- throw reporter.createError('Source entity with specified id does not exists', {
46
- statusCode: 400
47
- })
48
- }
49
-
50
- const onlyChildren = source.onlyChildren === true
51
-
52
- if (onlyChildren && source.entitySet !== 'folders') {
53
- throw reporter.createError('onlyChildren option can only be enabled when source is a folder')
54
- }
34
+ const isSingleSource = Array.isArray(source) ? source.length === 1 : true
35
+ const sourceList = isSingleSource ? [source] : source
55
36
 
56
37
  if (target.shortid === undefined) {
57
38
  throw reporter.createError('target should specify ".shortid"', {
@@ -65,259 +46,307 @@ module.exports = (reporter) => async ({ source, target, shouldCopy, shouldReplac
65
46
 
66
47
  const targetUpdateReferences = target.updateReferences === true && target.shortid != null
67
48
 
68
- let entitiesInHierarchy = []
69
-
70
- await collectEntitiesInHierarchy(
71
- reporter,
72
- entitiesInHierarchy,
73
- Object.assign(sourceEntity, { __entitySet: source.entitySet }),
74
- onlyChildren,
75
- req
76
- )
49
+ const allEntitiesInvolved = []
77
50
 
78
- let rootChildren
51
+ for (const sourceInfo of sourceList) {
52
+ const sourceCol = reporter.documentStore.collection(sourceInfo.entitySet)
79
53
 
80
- if (onlyChildren) {
81
- rootChildren = entitiesInHierarchy.filter((e) => {
82
- return e.folder.shortid === sourceEntity.shortid
83
- })
84
- }
54
+ if (!sourceCol) {
55
+ throw reporter.createError(`Invalid entity set "${sourceInfo.entitySet}" for source`, {
56
+ statusCode: 400
57
+ })
58
+ }
85
59
 
86
- // ignore if we are doing a move at the same level of hierarchy between source and target
87
- if (
88
- (sourceEntity.folder == null && target.shortid === null) ||
89
- (sourceEntity.folder != null &&
90
- target.shortid != null &&
91
- sourceEntity.folder.shortid === target.shortid)
92
- ) {
93
- return []
94
- }
60
+ const sourceEntity = await sourceCol.findOne({ _id: sourceInfo.id }, req)
95
61
 
96
- if (!shouldCopy) {
97
- // validates that we can't move entities from higher level
98
- // into lower level of the same hierarchy
99
- if (entitiesInHierarchy.some((e) => e.shortid === target.shortid)) {
100
- return []
62
+ if (!sourceEntity) {
63
+ throw reporter.createError('Source entity with specified id does not exists', {
64
+ statusCode: 400
65
+ })
101
66
  }
102
67
 
103
- let updateQ
68
+ const onlyChildren = sourceInfo.onlyChildren === true
104
69
 
105
- if (target.shortid === null) {
106
- updateQ = {
107
- $set: {
108
- folder: null
109
- }
110
- }
111
- } else {
112
- updateQ = {
113
- $set: {
114
- folder: {
115
- shortid: target.shortid
116
- }
117
- }
118
- }
70
+ if (onlyChildren && sourceInfo.entitySet !== 'folders') {
71
+ throw reporter.createError('onlyChildren option can only be enabled when source is a folder')
119
72
  }
120
73
 
121
- let sourceEntities
74
+ let entitiesInHierarchy = []
122
75
 
123
- if (!onlyChildren) {
124
- sourceEntities = [sourceEntity]
125
- } else {
126
- sourceEntities = rootChildren
127
- }
76
+ await collectEntitiesInHierarchy(
77
+ reporter,
78
+ entitiesInHierarchy,
79
+ Object.assign(sourceEntity, { __entitySet: sourceInfo.entitySet }),
80
+ onlyChildren,
81
+ req
82
+ )
128
83
 
129
- for (const entity of sourceEntities) {
130
- try {
131
- await reporter.documentStore.collection(entity.__entitySet).update({
132
- _id: entity._id
133
- }, updateQ, req)
134
- } catch (e) {
135
- if (e.code === 'DUPLICATED_ENTITY' && shouldReplace) {
136
- // replacing is not supported when it generates a conflict with folder
137
- if (e.existingEntityEntitySet === 'folders') {
138
- throw e
139
- }
84
+ let rootChildren
140
85
 
141
- const removeFolderQ = target.shortid === null ? { folder: null } : { folder: { shortid: target.shortid } }
86
+ if (onlyChildren) {
87
+ rootChildren = entitiesInHierarchy.filter((e) => {
88
+ return e.folder.shortid === sourceEntity.shortid
89
+ })
90
+ }
142
91
 
143
- await reporter.documentStore.collection(e.existingEntityEntitySet).remove({
144
- _id: e.existingEntity._id,
145
- ...removeFolderQ
146
- }, req)
92
+ // ignore if we are doing a copy/move at the same level of hierarchy between source and target
93
+ if (
94
+ (sourceEntity.folder == null && target.shortid === null) ||
95
+ (sourceEntity.folder != null &&
96
+ target.shortid != null &&
97
+ sourceEntity.folder.shortid === target.shortid)
98
+ ) {
99
+ continue
100
+ }
147
101
 
148
- await reporter.documentStore.collection(entity.__entitySet).update({
149
- _id: entity._id
150
- }, updateQ, req)
151
- } else {
152
- throw e
153
- }
102
+ if (!shouldCopy) {
103
+ // validates that we can't move entities from higher level
104
+ // into lower level of the same hierarchy
105
+ if (entitiesInHierarchy.some((e) => e.shortid === target.shortid)) {
106
+ continue
154
107
  }
155
- }
156
108
 
157
- const sourceEntityItems = entitiesInHierarchy.filter((e) => {
158
- return sourceEntities.find((childE) => childE._id === e._id) != null
159
- })
109
+ let updateQ
160
110
 
161
- for (const sourceEntityItem of sourceEntityItems) {
162
111
  if (target.shortid === null) {
163
- sourceEntityItem.folder = null
112
+ updateQ = {
113
+ $set: {
114
+ folder: null
115
+ }
116
+ }
164
117
  } else {
165
- sourceEntityItem.folder = {
166
- shortid: target.shortid
118
+ updateQ = {
119
+ $set: {
120
+ folder: {
121
+ shortid: target.shortid
122
+ }
123
+ }
167
124
  }
168
125
  }
169
- }
170
- } else {
171
- const entitiesInHierarchyByCollection = entitiesInHierarchy.reduce((acu, entity) => {
172
- acu[entity.__entitySet] = acu[entity.__entitySet] || []
173
- acu[entity.__entitySet].push(entity)
174
- return acu
175
- }, {})
176
-
177
- const entityReferencesMap = new WeakMap()
178
- const originalEntitiesNewMap = new WeakMap()
179
- const entityRecordNewValueMap = new WeakMap()
180
- const records = []
181
-
182
- // eslint-disable-next-line
183
- function createUpdateReferences (record) {
184
- return async (newEntity) => {
185
- const { entitySet, entity, originalEntity } = record
186
- const linkedEntities = entityReferencesMap.get(entity)
187
-
188
- if (linkedEntities.length === 0) {
189
- return
190
- }
191
126
 
192
- for (const { properties: linkedProperties, entity: originalLinkedEntity } of linkedEntities) {
193
- const currentNewLinkedEntity = originalEntitiesNewMap.get(originalLinkedEntity)
194
-
195
- if (entityRecordNewValueMap.has(currentNewLinkedEntity)) {
196
- const currentEntityProcessedNew = entityRecordNewValueMap.get(currentNewLinkedEntity)
197
- const currentEntityUpdate = {}
198
-
199
- // if we get here it means that the entity was already processed, so we need to
200
- // execute an update directly to the store
201
- for (const prop of linkedProperties) {
202
- // here we care to use the new object result because we want to preserve other values
203
- // in case the property is array with objects
204
- reporter.documentStore.updateReference(originalLinkedEntity.__entitySet, currentEntityProcessedNew, entitySet, { referenceProp: prop, referenceValue: originalEntity.shortid }, newEntity.shortid)
205
- const rootProp = prop.split('.')[0]
206
- currentEntityUpdate[rootProp] = currentEntityProcessedNew[rootProp]
127
+ let sourceEntities
128
+
129
+ if (!onlyChildren) {
130
+ sourceEntities = [sourceEntity]
131
+ } else {
132
+ sourceEntities = rootChildren
133
+ }
134
+
135
+ for (const entity of sourceEntities) {
136
+ try {
137
+ await reporter.documentStore.collection(entity.__entitySet).update({
138
+ _id: entity._id
139
+ }, updateQ, req)
140
+ } catch (e) {
141
+ if (e.code === 'DUPLICATED_ENTITY' && shouldReplace) {
142
+ // replacing is not supported when it generates a conflict with folder
143
+ if (e.existingEntityEntitySet === 'folders') {
144
+ throw e
207
145
  }
208
146
 
209
- await reporter.documentStore.collection(originalLinkedEntity.__entitySet).update({
210
- _id: currentEntityProcessedNew._id
211
- }, { $set: currentEntityUpdate }, req)
147
+ const removeFolderQ = target.shortid === null ? { folder: null } : { folder: { shortid: target.shortid } }
148
+
149
+ await reporter.documentStore.collection(e.existingEntityEntitySet).remove({
150
+ _id: e.existingEntity._id,
151
+ ...removeFolderQ
152
+ }, req)
153
+
154
+ await reporter.documentStore.collection(entity.__entitySet).update({
155
+ _id: entity._id
156
+ }, updateQ, req)
212
157
  } else {
213
- // here we care to update all properties to point to old reference value
214
- reporter.documentStore.updateReference(originalLinkedEntity.__entitySet, currentNewLinkedEntity, entitySet, { referenceValue: originalEntity.shortid }, newEntity.shortid)
158
+ throw e
215
159
  }
216
160
  }
217
161
  }
218
- }
219
162
 
220
- if (targetUpdateReferences) {
221
- const targetEntity = await reporter.documentStore.collection('folders').findOne({
222
- shortid: target.shortid
223
- }, req)
163
+ const sourceEntityItems = entitiesInHierarchy.filter((e) => {
164
+ return sourceEntities.find((childE) => childE._id === e._id) != null
165
+ })
224
166
 
225
- targetEntity.__entitySet = 'folders'
226
- entitiesInHierarchyByCollection.folders = entitiesInHierarchyByCollection.folders || []
227
- entitiesInHierarchyByCollection.folders.push(targetEntity)
228
- originalEntitiesNewMap.set(targetEntity, targetEntity)
229
- entityRecordNewValueMap.set(targetEntity, targetEntity)
230
- }
167
+ for (const sourceEntityItem of sourceEntityItems) {
168
+ if (target.shortid === null) {
169
+ sourceEntityItem.folder = null
170
+ } else {
171
+ sourceEntityItem.folder = {
172
+ shortid: target.shortid
173
+ }
174
+ }
175
+ }
176
+ } else {
177
+ const entitiesInHierarchyByCollection = entitiesInHierarchy.reduce((acu, entity) => {
178
+ acu[entity.__entitySet] = acu[entity.__entitySet] || []
179
+ acu[entity.__entitySet].push(entity)
180
+ return acu
181
+ }, {})
182
+
183
+ const entityReferencesMap = new WeakMap()
184
+ const originalEntitiesNewMap = new WeakMap()
185
+ const entityRecordNewValueMap = new WeakMap()
186
+ const records = []
187
+
188
+ // eslint-disable-next-line
189
+ function createUpdateReferences (record) {
190
+ return async (newEntity) => {
191
+ const { entitySet, entity, originalEntity } = record
192
+ const linkedEntities = entityReferencesMap.get(entity)
193
+
194
+ if (linkedEntities.length === 0) {
195
+ return
196
+ }
231
197
 
232
- for (const entity of entitiesInHierarchy) {
233
- const newEntity = {
234
- ...omit(entity, ['_id', 'shortid', '__entitySet'])
198
+ for (const { properties: linkedProperties, entity: originalLinkedEntity } of linkedEntities) {
199
+ const currentNewLinkedEntity = originalEntitiesNewMap.get(originalLinkedEntity)
200
+
201
+ if (entityRecordNewValueMap.has(currentNewLinkedEntity)) {
202
+ const currentEntityProcessedNew = entityRecordNewValueMap.get(currentNewLinkedEntity)
203
+ const currentEntityUpdate = {}
204
+
205
+ // if we get here it means that the entity was already processed, so we need to
206
+ // execute an update directly to the store
207
+ for (const prop of linkedProperties) {
208
+ // here we care to use the new object result because we want to preserve other values
209
+ // in case the property is array with objects
210
+ reporter.documentStore.updateReference(originalLinkedEntity.__entitySet, currentEntityProcessedNew, entitySet, { referenceProp: prop, referenceValue: originalEntity.shortid }, newEntity.shortid)
211
+ const rootProp = prop.split('.')[0]
212
+ currentEntityUpdate[rootProp] = currentEntityProcessedNew[rootProp]
213
+ }
214
+
215
+ await reporter.documentStore.collection(originalLinkedEntity.__entitySet).update({
216
+ _id: currentEntityProcessedNew._id
217
+ }, { $set: currentEntityUpdate }, req)
218
+ } else {
219
+ // here we care to update all properties to point to old reference value
220
+ reporter.documentStore.updateReference(originalLinkedEntity.__entitySet, currentNewLinkedEntity, entitySet, { referenceValue: originalEntity.shortid }, newEntity.shortid)
221
+ }
222
+ }
223
+ }
235
224
  }
236
225
 
237
- let isSourceEntityItem
226
+ if (targetUpdateReferences) {
227
+ const targetEntity = await reporter.documentStore.collection('folders').findOne({
228
+ shortid: target.shortid
229
+ }, req)
238
230
 
239
- if (!onlyChildren) {
240
- isSourceEntityItem = source.id === entity._id
241
- } else {
242
- isSourceEntityItem = rootChildren.find((e) => e._id === entity._id) != null
231
+ targetEntity.__entitySet = 'folders'
232
+ entitiesInHierarchyByCollection.folders = entitiesInHierarchyByCollection.folders || []
233
+ entitiesInHierarchyByCollection.folders.push(targetEntity)
234
+ originalEntitiesNewMap.set(targetEntity, targetEntity)
235
+ entityRecordNewValueMap.set(targetEntity, targetEntity)
243
236
  }
244
237
 
245
- if (isSourceEntityItem) {
246
- if (target.shortid === null) {
247
- newEntity.folder = null
238
+ for (const entity of entitiesInHierarchy) {
239
+ const newEntity = {
240
+ ...omit(entity, ['_id', 'shortid', '__entitySet'])
241
+ }
242
+
243
+ let isSourceEntityItem
244
+
245
+ if (!onlyChildren) {
246
+ isSourceEntityItem = sourceInfo.id === entity._id
248
247
  } else {
249
- newEntity.folder = {
250
- shortid: target.shortid
248
+ isSourceEntityItem = rootChildren.find((e) => e._id === entity._id) != null
249
+ }
250
+
251
+ if (isSourceEntityItem) {
252
+ if (target.shortid === null) {
253
+ newEntity.folder = null
254
+ } else {
255
+ newEntity.folder = {
256
+ shortid: target.shortid
257
+ }
258
+ }
259
+
260
+ // when we are copying with multi selection we want to normalize names
261
+ // so in case of duplicates we just add "(copy)" suffix, for single source
262
+ // we want the replace dialog
263
+ if (!isSingleSource) {
264
+ let copyAttempt = 0
265
+ let existsAtTarget
266
+
267
+ do {
268
+ existsAtTarget = await reporter.documentStore.collection(entity.__entitySet).findOne({
269
+ name: newEntity.name,
270
+ folder: newEntity.folder
271
+ })
272
+
273
+ if (existsAtTarget != null) {
274
+ copyAttempt++
275
+ newEntity.name = `${entity.name}(copy${copyAttempt > 1 ? copyAttempt : ''})`
276
+ }
277
+ } while (existsAtTarget != null)
251
278
  }
252
279
  }
253
- }
254
280
 
255
- const entitySet = entity.__entitySet
256
- newEntity.__entitySet = entitySet
281
+ const entitySet = entity.__entitySet
282
+ newEntity.__entitySet = entitySet
257
283
 
258
- const linkedEntities = reporter.documentStore.findLinkedEntitiesForReference(
259
- entitiesInHierarchyByCollection,
260
- entitySet,
261
- entity.shortid
262
- )
284
+ const linkedEntities = reporter.documentStore.findLinkedEntitiesForReference(
285
+ entitiesInHierarchyByCollection,
286
+ entitySet,
287
+ entity.shortid
288
+ )
263
289
 
264
- const record = {
265
- entitySet,
266
- originalEntity: entity,
267
- entity: newEntity
268
- }
290
+ const record = {
291
+ entitySet,
292
+ originalEntity: entity,
293
+ entity: newEntity
294
+ }
269
295
 
270
- record.updateReferences = createUpdateReferences(record)
296
+ record.updateReferences = createUpdateReferences(record)
271
297
 
272
- originalEntitiesNewMap.set(entity, newEntity)
273
- entityReferencesMap.set(newEntity, linkedEntities)
298
+ originalEntitiesNewMap.set(entity, newEntity)
299
+ entityReferencesMap.set(newEntity, linkedEntities)
274
300
 
275
- records.push(record)
276
- }
301
+ records.push(record)
302
+ }
277
303
 
278
- const processNewEntity = async (entitySet, entity) => {
279
- const newEntityFromStore = await reporter.documentStore.collection(entitySet).insert({
280
- ...omit(entity, ['__entitySet'])
281
- }, req)
304
+ const processNewEntity = async (entitySet, entity) => {
305
+ const newEntityFromStore = await reporter.documentStore.collection(entitySet).insert({
306
+ ...omit(entity, ['__entitySet'])
307
+ }, req)
282
308
 
283
- entityRecordNewValueMap.set(entity, newEntityFromStore)
284
- }
309
+ entityRecordNewValueMap.set(entity, newEntityFromStore)
310
+ }
285
311
 
286
- for (const record of records) {
287
- try {
288
- await processNewEntity(record.entitySet, record.entity)
289
- } catch (e) {
290
- if (e.code === 'DUPLICATED_ENTITY' && shouldReplace) {
291
- // replacing is not supported when it generates a conflict with folder
292
- if (e.existingEntityEntitySet === 'folders') {
293
- throw e
294
- }
312
+ for (const record of records) {
313
+ try {
314
+ await processNewEntity(record.entitySet, record.entity)
315
+ } catch (e) {
316
+ if (e.code === 'DUPLICATED_ENTITY' && shouldReplace) {
317
+ // replacing is not supported when it generates a conflict with folder
318
+ if (e.existingEntityEntitySet === 'folders') {
319
+ throw e
320
+ }
295
321
 
296
- const removeFolderQ = target.shortid === null ? { folder: null } : { folder: { shortid: target.shortid } }
322
+ const removeFolderQ = target.shortid === null ? { folder: null } : { folder: { shortid: target.shortid } }
297
323
 
298
- await reporter.documentStore.collection(e.existingEntityEntitySet).remove({
299
- _id: e.existingEntity._id,
300
- ...removeFolderQ
301
- }, req)
324
+ await reporter.documentStore.collection(e.existingEntityEntitySet).remove({
325
+ _id: e.existingEntity._id,
326
+ ...removeFolderQ
327
+ }, req)
302
328
 
303
- await processNewEntity(record.entitySet, record.entity)
304
- } else {
305
- throw e
329
+ await processNewEntity(record.entitySet, record.entity)
330
+ } else {
331
+ throw e
332
+ }
306
333
  }
334
+
335
+ await record.updateReferences(entityRecordNewValueMap.get(record.entity))
307
336
  }
308
337
 
309
- await record.updateReferences(entityRecordNewValueMap.get(record.entity))
338
+ entitiesInHierarchy = records.map((record) => ({
339
+ ...omit(entityRecordNewValueMap.get(record.entity), ['$entitySet', '__entitySet']),
340
+ __entitySet: record.entitySet
341
+ }))
310
342
  }
311
343
 
312
- entitiesInHierarchy = records.map((record) => ({
313
- ...omit(entityRecordNewValueMap.get(record.entity), ['$entitySet', '__entitySet']),
314
- __entitySet: record.entitySet
315
- }))
344
+ allEntitiesInvolved.push(...entitiesInHierarchy)
316
345
  }
317
346
 
318
347
  // return fresh version of creationDate, modificationDate in the records.
319
348
  // this helps with concurrent validation on studio
320
- await Promise.all(entitiesInHierarchy.map(async (entity) => {
349
+ await Promise.all(allEntitiesInvolved.map(async (entity) => {
321
350
  const entitySet = reporter.documentStore.model.entitySets[entity.__entitySet]
322
351
  const entityType = entitySet.entityTypeDef
323
352
  const projection = {}
@@ -350,5 +379,5 @@ module.exports = (reporter) => async ({ source, target, shouldCopy, shouldReplac
350
379
  }
351
380
  }))
352
381
 
353
- return entitiesInHierarchy
382
+ return allEntitiesInvolved
354
383
  }