@contrail/flexplm 1.1.64 → 1.1.66

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.
@@ -1,483 +1,486 @@
1
- import { FCConfig, EntityPayloadType } from '../interfaces/interfaces';
2
- import { DataConverter } from '../util/data-converter';
3
- import { TypeUtils } from '../util/type-utils';
4
- import { FlexPLMConnect,} from '../util/flexplm-connect';
5
- import { MapUtil } from '../util/map-utils';
6
- import { MapFileUtil } from '@contrail/transform-data';
7
- import { Entities } from '@contrail/sdk';
8
- import { TypeProperty } from '@contrail/types';
9
- import { TypeConversionUtils } from '../util/type-conversion-utils';
10
- import { EventShortMessageStatus } from '../util/event-short-message-status';
11
-
12
- const UNSUPPORTED_TYPE = 'Unsupported eventType.';
13
- export class IncomingEntityResponse {
14
- entity: any
15
- earlyReturn: any
16
- }
17
-
18
- export abstract class BaseEntityProcessor {
19
-
20
- protected typeUtil: TypeUtils;
21
- protected transformMapFile: string;
22
- protected entities;
23
- protected orgSlug: string;
24
- constructor(
25
- protected config: FCConfig,
26
- protected dc: DataConverter,
27
- protected mapFileUtil: MapFileUtil,
28
- protected baseType: string
29
- ) {
30
- this.typeUtil = new TypeUtils();
31
- this.transformMapFile = this.config?.transformMapFile;
32
- this.entities = new Entities();
33
- this.orgSlug = this.config?.orgSlug || 'unset-orgSlug';
34
- }
35
-
36
- // inbound
37
- async inbound(event: EntityPayloadType) {
38
- const eventType = event.eventType;
39
- console.log(`inbound entity: ${eventType}:${event.objectClass}`);
40
-
41
- switch (eventType) {
42
- case 'PERSIST':
43
- return await this.handleIncomingUpsert(event);
44
- case 'DELETE':
45
- return await this.handleIncomingDelete(event);
46
- default:
47
- console.error(UNSUPPORTED_TYPE);
48
- return {
49
- status: 500,
50
- data: { UNSUPPORTED_TYPE }
51
- };
52
- }
53
- }
54
-
55
- async handleIncomingUpsert (event: EntityPayloadType) {
56
- const inboundData = await this.getTransformedData(event);
57
- const incomingEntityResponse = await this.getIncomingEntity(event, inboundData);
58
- // This case means there was an early return in the getIncomingEntity method
59
- if (incomingEntityResponse.earlyReturn) {
60
- const statusMsg = this.getInboundStatusMessage({
61
- status: EventShortMessageStatus.FAILURE,
62
- statusMessage: incomingEntityResponse.earlyReturn.shortStatusMessage || '',
63
- objectClass: event.objectClass,
64
- federatedId: event.federatedId
65
- });
66
- console.log(statusMsg);
67
- return incomingEntityResponse.earlyReturn;
68
- }
69
-
70
- const entity = incomingEntityResponse.entity;
71
- if (!entity) {
72
- const createEntityResponse = await this.getCreateEntity(inboundData);
73
- if (createEntityResponse.earlyReturn) {
74
- const status = (createEntityResponse.earlyReturn.shortStatusMessage === EventShortMessageStatus.NOT_CREATABLE)
75
- ? EventShortMessageStatus.SUCCESS
76
- : EventShortMessageStatus.FAILURE;
77
- const statusMsg = this.getInboundStatusMessage({
78
- status,
79
- statusMessage: createEntityResponse.earlyReturn.shortStatusMessage ||'',
80
- objectClass: event.objectClass,
81
- federatedId: event.federatedId
82
- });
83
- console.log(statusMsg);
84
- return createEntityResponse.earlyReturn;
85
- }
86
- const createdEntity = await this.createEntity(this.baseType, createEntityResponse.entity);
87
- const statusMsg = this.getInboundStatusMessage({
88
- status: EventShortMessageStatus.SUCCESS,
89
- statusMessage: EventShortMessageStatus.CREATED,
90
- objectClass: event.objectClass,
91
- entityId: 'id',
92
- federatedId: event.federatedId
93
- });
94
- console.log(statusMsg);
95
- return createdEntity;
96
- }
97
-
98
- const diffs = await this.getUpdatesForEntity(entity, inboundData);
99
- if(Object.getOwnPropertyNames(diffs).length == 0){
100
- const statusMsg = this.getInboundStatusMessage({
101
- status: EventShortMessageStatus.SUCCESS,
102
- statusMessage: EventShortMessageStatus.NO_CHANGES,
103
- objectClass: event.objectClass,
104
- entityId: entity.id,
105
- federatedId: event.federatedId
106
- });
107
- console.log(statusMsg);
108
- const message = 'No Changes to persist for entity: ' + entity.id;
109
- return {
110
- status: 200,
111
- data: {message}
112
- };
113
- }
114
-
115
- const updatedEntity = await this.updateEntity(this.baseType, entity, diffs);
116
- const statusMsg = this.getInboundStatusMessage({
117
- status: EventShortMessageStatus.SUCCESS,
118
- statusMessage: EventShortMessageStatus.UPDATED,
119
- objectClass: event.objectClass,
120
- entityId: entity.id,
121
- federatedId: event.federatedId
122
- });
123
- console.log(statusMsg);
124
- return updatedEntity;
125
- }
126
-
127
- getInboundStatusMessage(statusObject){
128
- return 'BaseEntityProcessor: inbound: status: ' + statusObject.status
129
- + ', statusMessage: ' + statusObject.statusMessage
130
- + ', entityType: ' + this.baseType
131
- + ', entityId: ' + statusObject.entityId
132
- + ', objectClass: ' + statusObject.objectClass
133
- + ', federatedId: ' + statusObject.federatedId
134
- + ', orgSlug: ' + this.orgSlug;
135
- }
136
-
137
- /**This will query for the entity, and handle post-processing
138
- * of any critieria that is defined at the sub-type level.
139
- * Because sub-type criteria can't be used in the search done
140
- * on the server. This is expected to be called by getIncomingEntity().
141
- *
142
- * @param entityType: the root type of the entity
143
- * @param entityTypePath: the full type path of the entity. Ex: custom-entity:sample
144
- * @param propertyCriteria: all the criteria to search for the entity
145
- * @returns the entities that match the criteria
146
- */
147
- async queryEntityWithSubTypeCriteria(entityType: string, entityTypePath: string, propertyCriteria: any): Promise<any[]>{
148
- //allCriteria; identifierKeys; entityType; entityTypePath
149
- if(!entityType || !entityTypePath){
150
- throw new Error('type and entityTypePath must be defined');
151
- }
152
- if(!propertyCriteria || Object.getOwnPropertyNames(propertyCriteria).length == 0){
153
- throw new Error('propertyCriteria must be defined and have at least one property');
154
- }
155
-
156
- const {rootTypeCriteria, subTypeCriteria} = await this.getCriteriaForEntity(entityType, entityTypePath, propertyCriteria);
157
-
158
- const returnedEntities = await this.dc.getAllObjectReferences(entityType, rootTypeCriteria, subTypeCriteria);
159
-
160
- return returnedEntities;
161
- }
162
-
163
- /** This is to get the criteria for the entity that is being processed.
164
- * This is to be overridden for item & project-item because of the need for
165
- * setting the roles criteria.
166
- *
167
- * @param entityType: the root type of the entity
168
- * @param entityTypePath: the full type path of the entity. Ex: custom-entity:sample
169
- * @param propertyCriteria: all the criteria to search for the entity
170
- * @returns the criteria for the entity
171
- */
172
- async getCriteriaForEntity(entityType: string, entityTypePath: string, propertyCriteria: any): Promise<any>{
173
- if(!entityType || !entityTypePath){
174
- throw new Error('type and entityTypePath must be defined');
175
- }
176
- if(!propertyCriteria || Object.getOwnPropertyNames(propertyCriteria).length == 0){
177
- throw new Error('propertyCriteria must be defined and have at least one property');
178
- }
179
- const rootType = await this.typeUtil.getByRootAndPath({root: entityType});
180
- const rootTypePropertyKeys = await this.getRootTypePropertyKeys(rootType, propertyCriteria);
181
-
182
- const rootTypeCriteria = {};
183
- const subTypeCriteria = {};
184
- if(entityType !== entityTypePath){
185
- subTypeCriteria['typePath'] = entityTypePath;
186
- }
187
- for(const key in propertyCriteria){
188
- if(rootTypePropertyKeys.includes(key)){
189
- rootTypeCriteria[key] = propertyCriteria[key];
190
- }else{
191
- subTypeCriteria[key] = propertyCriteria[key];
192
- }
193
- }
194
-
195
- return {rootTypeCriteria, subTypeCriteria};
196
- }
197
-
198
- /** This is to get the properties that are owned by the root type
199
- * This needs to be overridded for multi-level types, such as item
200
- * and project-item. And for those types, the propertyCriteria
201
- * will be needed to determine the correct level.
202
- *
203
- * @param rootType: the full root type entity for the processed entity
204
- * @param propertyCriteria: the criteria to determine the correct level (unused for single level types)
205
- * @returns: string[] of the property keys
206
- */
207
- getRootTypePropertyKeys(rootType, propertyCriteria = null): string[] {
208
- const props: TypeProperty[] = rootType['typeProperties'];
209
- const rootTypePropertyKeys = props.map(prop => prop.slug);
210
-
211
- return rootTypePropertyKeys;
212
- }
213
-
214
-
215
- async handleIncomingDelete(event) {
216
- console.warn('delete is not configured', event);
217
- }
218
-
219
- async getTransformedData(event) {
220
- let inboundData = event.data;
221
- console.debug('inboundData: ' + JSON.stringify(inboundData));
222
-
223
- const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, inboundData, TypeConversionUtils.FLEX2VIBE_DIRECTION);
224
- inboundData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, inboundData, mapKey, TypeConversionUtils.FLEX2VIBE_DIRECTION);
225
- console.debug('Transformed-inboundData: ' + JSON.stringify(inboundData));
226
-
227
-
228
-
229
- return inboundData;
230
- }
231
-
232
- async getUpdatesForEntity(entity, inboundData) {
233
- const vibeOwningKeys = await this.getVibeOwningKeys(entity);
234
- let updates = {
235
- typeId: entity.typeId,
236
- roles: entity.roles,
237
- id: entity.id,
238
- };
239
-
240
- updates = await this.dc.setEntityValues(updates, inboundData, vibeOwningKeys);
241
- for(const prop of ['typeId', 'roles', 'id']){
242
- delete updates[prop];
243
- }
244
-
245
- return this.dc.getPersistableChanges(entity, updates);
246
- }
247
-
248
- async getVibeOwningKeys(entity) {
249
- let vibeOwningKeys = [];
250
- if (this.transformMapFile && entity) {
251
- //Technically the transform is flex->vibe. But the vibe entity being updated was passed in,
252
- // so we use VIBE2FLEX_DIRECTION to get the mapKey
253
- const mapKey = await TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
254
-
255
- const mapSection = await MapUtil.getFullMapSection(this.transformMapFile, this.mapFileUtil, mapKey);
256
- vibeOwningKeys = mapSection?.vibeOwningKeys || [];
257
- }
258
- console.debug('vibeOwningKeys: ' + vibeOwningKeys);
259
- return vibeOwningKeys;
260
- }
261
-
262
- async getVibeOwningKeysFromInbound(entity) {
263
- let vibeOwningKeys = [];
264
- if (this.transformMapFile && entity) {
265
- const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, entity, TypeConversionUtils.FLEX2VIBE_DIRECTION);
266
-
267
- const mapSection = await MapUtil.getFullMapSection(this.transformMapFile, this.mapFileUtil, mapKey);
268
- vibeOwningKeys = mapSection?.vibeOwningKeys || [];
269
- }
270
- console.debug('vibeOwningKeys: ' + vibeOwningKeys);
271
- return vibeOwningKeys;
272
- }
273
-
274
- async createEntity(entityName, changes) {
275
- const options = {
276
- entityName: entityName,
277
- object: changes,
278
- };
279
- console.log("createEntity: " + JSON.stringify(options));
280
-
281
- return await new Entities().create(options);
282
- }
283
-
284
- async updateEntity(entityName, entity, diffs) {
285
- const options = {
286
- entityName: entityName,
287
- id: entity['id'],
288
- object: diffs
289
- };
290
- console.log('updateEntity: ' + JSON.stringify(options));
291
-
292
- return await new Entities().update(options);
293
- }
294
-
295
- // This method must be implemented by derived classes
296
- protected abstract getIncomingEntity(event, inboundData): Promise<IncomingEntityResponse>;
297
- protected abstract getCreateEntity(inboundData): Promise<IncomingEntityResponse>;
298
-
299
- // outbound
300
-
301
- async outbound(event) {
302
- const entityType = event.entityType;
303
- const eventType = event.eventType;
304
- const entityId = event.id;
305
- console.log(`outbound: ${entityType}:${entityId}`);
306
-
307
- switch (eventType) {
308
- case 'update':
309
- case 'create':
310
- return await this.handleOutgoingUpsert(entityType, event);
311
- case 'delete':
312
- return await this.handleOutgoingDelete(entityType, event);
313
- case 'sendUpsertToFlexPLM':
314
- return await this.sendUpsertToFlexPLM(event);
315
- default:
316
- console.log(UNSUPPORTED_TYPE);
317
- return {
318
- status: 500,
319
- data: { UNSUPPORTED_TYPE }
320
- };
321
- }
322
- }
323
-
324
- async handleOutgoingUpsert(entityType, event) {
325
- const objectClass = await TypeConversionUtils.getObjectClass(this.transformMapFile, this.mapFileUtil, event.newData);
326
- if (!objectClass) {
327
- const message = 'ObjectClass must have a value.';
328
- console.log(message);
329
- return {
330
- status: 500,
331
- data: { message }
332
- };
333
- }
334
-
335
- try {
336
- const payload = await this.getOutgoingUpsertPayload(entityType, event);
337
- const flexResponse: any = await new FlexPLMConnect(this.config).sendToFlexPLM(payload);
338
-
339
- const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
340
- if(outboundEntityUpdates){
341
- flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
342
- }
343
-
344
- const statusMsg = 'BaseEntityProcessor: outbound: status: ' + EventShortMessageStatus.SUCCESS
345
- + ', statusMessage: '+ flexResponse.status
346
- + ', entityType: ' + this.baseType
347
- + ', entityId: ' + event.id
348
- + ', objectClass: ' + payload.objectClass
349
- + ', updateFromResponse: ' + (( outboundEntityUpdates &&Object.keys(outboundEntityUpdates).length > 0)? 'true' : 'false')
350
- + ', orgSlug: ' + this.orgSlug;
351
- console.log(statusMsg);
352
-
353
- return flexResponse;
354
-
355
- } catch(e){
356
- const statusMsg = 'BaseEntityProcessor: outbound: status: '+ EventShortMessageStatus.FAILURE
357
- + ', statusMessage: ' + e.httpResponseStatus
358
- + ', entityType: ' + this.baseType
359
- + ', entityId: ' + event.id
360
- + ', objectClass: ' + objectClass
361
- + ', updateFromResponse: ' + 'false'
362
- + ', orgSlug: ' + this.orgSlug;
363
- console.log(statusMsg);
364
- throw e;
365
- }
366
- }
367
-
368
- async getOutboundEntityUpdates(event, flexResponse): Promise<any> {
369
- const payload = flexResponse?.data?.payload;
370
- const flexPayload = (payload)? payload[0] : undefined;
371
- let outboundEntityUpdates = undefined;
372
- if(flexPayload && 'OK' === flexPayload.status) {
373
- const inboundData = await this.getTransformedData(flexPayload);
374
- outboundEntityUpdates = await this.getUpdatesForEntity(event.newData, inboundData)
375
- }
376
- return outboundEntityUpdates;
377
- }
378
-
379
- async handleOutgoingDelete(entityType, event) {
380
- console.warn('delete is not configured', entityType, event.oldData);
381
- }
382
-
383
- // This method must be implemented by derived classes
384
- protected abstract getOutgoingUpsertPayload(entityType, event): Promise<EntityPayloadType>;
385
-
386
- /** Create a new event-workflow-request to rerun sending the entity to FlexPLM
387
- * The event must contain any information needed to ensure it is put in the correct queue for the entity
388
- *
389
- * @param triggerKey Ex: event.entityType + '|sendUpsertToFlexPLM'
390
- * @param event
391
- * @returns
392
- */
393
-
394
- protected async triggerNewEvent(triggerKey: string, event: any) {
395
- const newEvent = {
396
- entityName: 'event-workflow-request',
397
- object: {
398
- triggerKey,
399
- event
400
- }
401
- };
402
- const response = await this.entities.create(newEvent);
403
-
404
- return response;
405
- }
406
-
407
- /** Sends the current state of the entity to FlexPLM.
408
- * So any changes made in Vibe between the event being generated and the event being processed are sent to FlexPLM.
409
- *
410
- * @param event must contain entityType, id; which are used to query for the entity
411
- * @returns results of sending the entity to FlexPLM
412
- */
413
- protected async sendUpsertToFlexPLM(event) {
414
- const payload = await this.getEntityCurrentStateUpsertPayload(event);
415
- if(!payload){
416
- const message = 'No payload to send to FlexPLM';
417
- console.log(message);
418
- return {
419
- status: 500,
420
- data: {message}
421
- };
422
- };
423
- let objectClass = payload.objectClass;
424
- try {
425
- const flexResponse: any = await new FlexPLMConnect(this.config).sendToFlexPLM(payload);
426
-
427
- const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
428
- if(outboundEntityUpdates){
429
- flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
430
- }
431
-
432
- const statusMsg = 'BaseEntityProcessor: outbound: status: ' + EventShortMessageStatus.SUCCESS
433
- + ', statusMessage: ' + flexResponse.status
434
- + ', entityType: ' + this.baseType
435
- + ', entityId: ' + event.id
436
- + ', objectClass: ' + objectClass
437
- + ', updateFromResponse: ' + ((outboundEntityUpdates &&Object.keys(outboundEntityUpdates).length > 0)? 'true' : 'false')
438
- + ', orgSlug: ' + this.orgSlug;
439
- console.log(statusMsg);
440
- return flexResponse;
441
- }catch(e){
442
- const statusMsg = 'BaseEntityProcessor: outbound: status: '+ EventShortMessageStatus.FAILURE
443
- + ', statusMessage: ' + e.httpResponseStatus
444
- + ', entityType: ' + this.baseType
445
- + ', entityId: ' + event.id
446
- + ', objectClass: ' + objectClass
447
- + ', updateFromResponse: ' + 'false'
448
- + ', orgSlug: ' + this.orgSlug;
449
- console.log(statusMsg);
450
- throw e;
451
- }
452
- }
453
-
454
- /** Generates the payload to send to FlexPLM, based on the current state of the entity.
455
- * The current state of the entity are used as the newData and oldData; which is passed
456
- * to getOutgoingUpsertPayload to generate the payload.
457
- * @param event information about the item to send to FlexPLM
458
- * @returns The payload to send to FlexPLM
459
- */
460
- protected async getEntityCurrentStateUpsertPayload(event: any): Promise<EntityPayloadType> {
461
-
462
- const id = event.id;
463
- if(!id){
464
- return undefined;
465
- }
466
-
467
- const entity = await this.entities.get({
468
- entityName: this.baseType,
469
- id
470
- });
471
-
472
- if(!entity){
473
- return undefined;
474
- }
475
-
476
- event.newData = entity;
477
- event.oldData = entity;
478
-
479
- const payload = await this.getOutgoingUpsertPayload(this.baseType, event);
480
-
481
- return payload;
482
- }
483
- }
1
+ import { FCConfig, EntityPayloadType } from '../interfaces/interfaces';
2
+ import { DataConverter } from '../util/data-converter';
3
+ import { TypeUtils } from '../util/type-utils';
4
+ import { FlexPLMConnect,} from '../util/flexplm-connect';
5
+ import { MapUtil } from '../util/map-utils';
6
+ import { MapFileUtil } from '@contrail/transform-data';
7
+ import { Entities } from '@contrail/sdk';
8
+ import { TypeProperty } from '@contrail/types';
9
+ import { TypeConversionUtils } from '../util/type-conversion-utils';
10
+ import { EventShortMessageStatus } from '../util/event-short-message-status';
11
+
12
+ const UNSUPPORTED_TYPE = 'Unsupported eventType.';
13
+ export class IncomingEntityResponse {
14
+ entity: any
15
+ earlyReturn: any
16
+ }
17
+
18
+ export abstract class BaseEntityProcessor {
19
+
20
+ protected typeUtil: TypeUtils;
21
+ protected transformMapFile: string;
22
+ protected entities;
23
+ protected orgSlug: string;
24
+ constructor(
25
+ protected config: FCConfig,
26
+ protected dc: DataConverter,
27
+ protected mapFileUtil: MapFileUtil,
28
+ protected baseType: string
29
+ ) {
30
+ this.typeUtil = new TypeUtils();
31
+ this.transformMapFile = this.config?.transformMapFile;
32
+ this.entities = new Entities();
33
+ this.orgSlug = this.config?.orgSlug || 'unset-orgSlug';
34
+ }
35
+
36
+ // inbound
37
+ async inbound(event: EntityPayloadType) {
38
+ const eventType = event.eventType;
39
+ console.log(`inbound entity: ${eventType}:${event.objectClass}`);
40
+
41
+ switch (eventType) {
42
+ case 'PERSIST':
43
+ return await this.handleIncomingUpsert(event);
44
+ case 'DELETE':
45
+ return await this.handleIncomingDelete(event);
46
+ default:
47
+ console.error(UNSUPPORTED_TYPE);
48
+ return {
49
+ status: 500,
50
+ data: { UNSUPPORTED_TYPE }
51
+ };
52
+ }
53
+ }
54
+
55
+ async handleIncomingUpsert (event: EntityPayloadType) {
56
+ const inboundData = await this.getTransformedData(event);
57
+ const incomingEntityResponse = await this.getIncomingEntity(event, inboundData);
58
+ // This case means there was an early return in the getIncomingEntity method
59
+ if (incomingEntityResponse.earlyReturn) {
60
+ const statusMsg = this.getInboundStatusMessage({
61
+ status: EventShortMessageStatus.FAILURE,
62
+ statusMessage: incomingEntityResponse.earlyReturn.shortStatusMessage || '',
63
+ objectClass: event.objectClass,
64
+ federatedId: event.federatedId
65
+ });
66
+ console.log(statusMsg);
67
+ return incomingEntityResponse.earlyReturn;
68
+ }
69
+
70
+ const entity = incomingEntityResponse.entity;
71
+ if (!entity) {
72
+ const createEntityResponse = await this.getCreateEntity(inboundData);
73
+ if (createEntityResponse.earlyReturn) {
74
+ const status = (createEntityResponse.earlyReturn.shortStatusMessage === EventShortMessageStatus.NOT_CREATABLE)
75
+ ? EventShortMessageStatus.SUCCESS
76
+ : EventShortMessageStatus.FAILURE;
77
+ const statusMsg = this.getInboundStatusMessage({
78
+ status,
79
+ statusMessage: createEntityResponse.earlyReturn.shortStatusMessage ||'',
80
+ objectClass: event.objectClass,
81
+ federatedId: event.federatedId
82
+ });
83
+ console.log(statusMsg);
84
+ return createEntityResponse.earlyReturn;
85
+ }
86
+ const createdEntity = await this.createEntity(this.baseType, createEntityResponse.entity);
87
+ const statusMsg = this.getInboundStatusMessage({
88
+ status: EventShortMessageStatus.SUCCESS,
89
+ statusMessage: EventShortMessageStatus.CREATED,
90
+ objectClass: event.objectClass,
91
+ entityId: 'id',
92
+ federatedId: event.federatedId
93
+ });
94
+ console.log(statusMsg);
95
+ return createdEntity;
96
+ }
97
+
98
+ const diffs = await this.getUpdatesForEntity(entity, inboundData);
99
+ if(Object.getOwnPropertyNames(diffs).length == 0){
100
+ const statusMsg = this.getInboundStatusMessage({
101
+ status: EventShortMessageStatus.SUCCESS,
102
+ statusMessage: EventShortMessageStatus.NO_CHANGES,
103
+ objectClass: event.objectClass,
104
+ entityId: entity.id,
105
+ federatedId: event.federatedId
106
+ });
107
+ console.log(statusMsg);
108
+ const message = 'No Changes to persist for entity: ' + entity.id;
109
+ return {
110
+ status: 200,
111
+ data: {message}
112
+ };
113
+ }
114
+
115
+ const updatedEntity = await this.updateEntity(this.baseType, entity, diffs);
116
+ const statusMsg = this.getInboundStatusMessage({
117
+ status: EventShortMessageStatus.SUCCESS,
118
+ statusMessage: EventShortMessageStatus.UPDATED,
119
+ objectClass: event.objectClass,
120
+ entityId: entity.id,
121
+ federatedId: event.federatedId
122
+ });
123
+ console.log(statusMsg);
124
+ return updatedEntity;
125
+ }
126
+
127
+ getInboundStatusMessage(statusObject){
128
+ return 'BaseEntityProcessor: inbound: status: ' + statusObject.status
129
+ + ', statusMessage: ' + statusObject.statusMessage
130
+ + ', entityType: ' + this.baseType
131
+ + ', entityId: ' + statusObject.entityId
132
+ + ', objectClass: ' + statusObject.objectClass
133
+ + ', federatedId: ' + statusObject.federatedId
134
+ + ', orgSlug: ' + this.orgSlug;
135
+ }
136
+
137
+ /**This will query for the entity, and handle post-processing
138
+ * of any critieria that is defined at the sub-type level.
139
+ * Because sub-type criteria can't be used in the search done
140
+ * on the server. This is expected to be called by getIncomingEntity().
141
+ *
142
+ * @param entityType: the root type of the entity
143
+ * @param entityTypePath: the full type path of the entity. Ex: custom-entity:sample
144
+ * @param propertyCriteria: all the criteria to search for the entity
145
+ * @returns the entities that match the criteria
146
+ */
147
+ async queryEntityWithSubTypeCriteria(entityType: string, entityTypePath: string, propertyCriteria: any): Promise<any[]>{
148
+ //allCriteria; identifierKeys; entityType; entityTypePath
149
+ if(!entityType || !entityTypePath){
150
+ throw new Error('type and entityTypePath must be defined');
151
+ }
152
+ if(!propertyCriteria || Object.getOwnPropertyNames(propertyCriteria).length == 0){
153
+ throw new Error('propertyCriteria must be defined and have at least one property');
154
+ }
155
+
156
+ const {rootTypeCriteria, subTypeCriteria} = await this.getCriteriaForEntity(entityType, entityTypePath, propertyCriteria);
157
+
158
+ const returnedEntities = await this.dc.getAllObjectReferences(entityType, rootTypeCriteria, subTypeCriteria);
159
+
160
+ return returnedEntities;
161
+ }
162
+
163
+ /** This is to get the criteria for the entity that is being processed.
164
+ * This is to be overridden for item & project-item because of the need for
165
+ * setting the roles criteria.
166
+ *
167
+ * @param entityType: the root type of the entity
168
+ * @param entityTypePath: the full type path of the entity. Ex: custom-entity:sample
169
+ * @param propertyCriteria: all the criteria to search for the entity
170
+ * @returns the criteria for the entity
171
+ */
172
+ async getCriteriaForEntity(entityType: string, entityTypePath: string, propertyCriteria: any): Promise<any>{
173
+ if(!entityType || !entityTypePath){
174
+ throw new Error('type and entityTypePath must be defined');
175
+ }
176
+ if(!propertyCriteria || Object.getOwnPropertyNames(propertyCriteria).length == 0){
177
+ throw new Error('propertyCriteria must be defined and have at least one property');
178
+ }
179
+ const rootType = await this.typeUtil.getByRootAndPath({root: entityType});
180
+ const rootTypePropertyKeys = await this.getRootTypePropertyKeys(rootType, propertyCriteria);
181
+
182
+ const rootTypeCriteria = {};
183
+ const subTypeCriteria = {};
184
+ if(entityType !== entityTypePath){
185
+ subTypeCriteria['typePath'] = entityTypePath;
186
+ }
187
+ for(const key in propertyCriteria){
188
+ if(rootTypePropertyKeys.includes(key)){
189
+ rootTypeCriteria[key] = propertyCriteria[key];
190
+ }else{
191
+ subTypeCriteria[key] = propertyCriteria[key];
192
+ }
193
+ }
194
+
195
+ return {rootTypeCriteria, subTypeCriteria};
196
+ }
197
+
198
+ /** This is to get the properties that are owned by the root type
199
+ * This needs to be overridded for multi-level types, such as item
200
+ * and project-item. And for those types, the propertyCriteria
201
+ * will be needed to determine the correct level.
202
+ *
203
+ * @param rootType: the full root type entity for the processed entity
204
+ * @param propertyCriteria: the criteria to determine the correct level (unused for single level types)
205
+ * @returns: string[] of the property keys
206
+ */
207
+ getRootTypePropertyKeys(rootType, propertyCriteria = null): string[] {
208
+ const props: TypeProperty[] = rootType['typeProperties'];
209
+ const rootTypePropertyKeys = props.map(prop => prop.slug);
210
+
211
+ return rootTypePropertyKeys;
212
+ }
213
+
214
+
215
+ async handleIncomingDelete(event) {
216
+ console.warn('delete is not configured', event);
217
+ }
218
+
219
+ async getTransformedData(event) {
220
+ let inboundData = event.data;
221
+ console.debug('inboundData: ' + JSON.stringify(inboundData));
222
+
223
+ const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, inboundData, TypeConversionUtils.FLEX2VIBE_DIRECTION);
224
+ inboundData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, inboundData, mapKey, TypeConversionUtils.FLEX2VIBE_DIRECTION);
225
+ console.debug('Transformed-inboundData: ' + JSON.stringify(inboundData));
226
+
227
+
228
+
229
+ return inboundData;
230
+ }
231
+
232
+ async getUpdatesForEntity(entity, inboundData) {
233
+ const vibeOwningKeys = await this.getVibeOwningKeys(entity);
234
+ let updates = {
235
+ typeId: entity.typeId,
236
+ roles: entity.roles,
237
+ id: entity.id,
238
+ };
239
+
240
+ updates = await this.dc.setEntityValues(updates, inboundData, vibeOwningKeys);
241
+ for(const prop of ['typeId', 'roles', 'id']){
242
+ delete updates[prop];
243
+ }
244
+
245
+ return this.dc.getPersistableChanges(entity, updates);
246
+ }
247
+
248
+ async getVibeOwningKeys(entity) {
249
+ let vibeOwningKeys = [];
250
+ if (this.transformMapFile && entity) {
251
+ //Technically the transform is flex->vibe. But the vibe entity being updated was passed in,
252
+ // so we use VIBE2FLEX_DIRECTION to get the mapKey
253
+ const mapKey = await TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
254
+
255
+ const mapSection = await MapUtil.getFullMapSection(this.transformMapFile, this.mapFileUtil, mapKey);
256
+ vibeOwningKeys = mapSection?.vibeOwningKeys || [];
257
+ }
258
+ console.debug('vibeOwningKeys: ' + vibeOwningKeys);
259
+ return vibeOwningKeys;
260
+ }
261
+
262
+ async getVibeOwningKeysFromInbound(entity) {
263
+ let vibeOwningKeys = [];
264
+ if (this.transformMapFile && entity) {
265
+ const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, entity, TypeConversionUtils.FLEX2VIBE_DIRECTION);
266
+
267
+ const mapSection = await MapUtil.getFullMapSection(this.transformMapFile, this.mapFileUtil, mapKey);
268
+ vibeOwningKeys = mapSection?.vibeOwningKeys || [];
269
+ }
270
+ console.debug('vibeOwningKeys: ' + vibeOwningKeys);
271
+ return vibeOwningKeys;
272
+ }
273
+
274
+ async createEntity(entityName, changes) {
275
+ const options = {
276
+ entityName: entityName,
277
+ object: changes,
278
+ };
279
+ console.log("createEntity: " + JSON.stringify(options));
280
+
281
+ return await new Entities().create(options);
282
+ }
283
+
284
+ async updateEntity(entityName, entity, diffs) {
285
+ const options = {
286
+ entityName: entityName,
287
+ id: entity['id'],
288
+ object: diffs
289
+ };
290
+ console.log('updateEntity: ' + JSON.stringify(options));
291
+
292
+ return await new Entities().update(options);
293
+ }
294
+
295
+ // This method must be implemented by derived classes
296
+ protected abstract getIncomingEntity(event, inboundData): Promise<IncomingEntityResponse>;
297
+ protected abstract getCreateEntity(inboundData): Promise<IncomingEntityResponse>;
298
+
299
+ // outbound
300
+
301
+ async outbound(event) {
302
+ const entityType = event.entityType;
303
+ const eventType = event.eventType;
304
+ const entityId = event.id;
305
+ console.log(`outbound: ${entityType}:${entityId}`);
306
+
307
+ switch (eventType) {
308
+ case 'update':
309
+ case 'create':
310
+ return await this.handleOutgoingUpsert(entityType, event);
311
+ case 'delete':
312
+ return await this.handleOutgoingDelete(entityType, event);
313
+ case 'sendUpsertToFlexPLM':
314
+ return await this.sendUpsertToFlexPLM(event);
315
+ default:
316
+ console.log(UNSUPPORTED_TYPE);
317
+ return {
318
+ status: 500,
319
+ data: { UNSUPPORTED_TYPE }
320
+ };
321
+ }
322
+ }
323
+
324
+ async handleOutgoingUpsert(entityType, event) {
325
+ const objectClass = await TypeConversionUtils.getObjectClass(this.transformMapFile, this.mapFileUtil, event.newData);
326
+ if (!objectClass) {
327
+ const message = 'ObjectClass must have a value.';
328
+ console.log(message);
329
+ return {
330
+ status: 500,
331
+ data: { message }
332
+ };
333
+ }
334
+
335
+ try {
336
+ const payload = await this.getOutgoingUpsertPayload(entityType, event);
337
+ const flexResponse: any = await new FlexPLMConnect(this.config).sendToFlexPLM(payload);
338
+
339
+ const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
340
+ if(outboundEntityUpdates){
341
+ flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
342
+ }
343
+
344
+ const statusMsg = 'BaseEntityProcessor: outbound: status: ' + EventShortMessageStatus.SUCCESS
345
+ + ', statusMessage: '+ flexResponse.status
346
+ + ', entityType: ' + this.baseType
347
+ + ', entityId: ' + event.id
348
+ + ', objectClass: ' + payload.objectClass
349
+ + ', updateFromResponse: ' + (( outboundEntityUpdates &&Object.keys(outboundEntityUpdates).length > 0)? 'true' : 'false')
350
+ + ', orgSlug: ' + this.orgSlug;
351
+ console.log(statusMsg);
352
+
353
+ return flexResponse;
354
+
355
+ } catch(e){
356
+ const statusMsg = 'BaseEntityProcessor: outbound: status: '+ EventShortMessageStatus.FAILURE
357
+ + ', statusMessage: ' + e.httpResponseStatus
358
+ + ', entityType: ' + this.baseType
359
+ + ', entityId: ' + event.id
360
+ + ', objectClass: ' + objectClass
361
+ + ', updateFromResponse: ' + 'false'
362
+ + ', orgSlug: ' + this.orgSlug;
363
+ console.log(statusMsg);
364
+ throw e;
365
+ }
366
+ }
367
+
368
+ async getOutboundEntityUpdates(event, flexResponse): Promise<any> {
369
+ const payload = flexResponse?.data?.payload;
370
+ const flexPayload = (payload)? payload[0] : undefined;
371
+ let outboundEntityUpdates = undefined;
372
+ if(flexPayload && 'OK' === flexPayload.status) {
373
+ if(flexPayload.data && !flexPayload.data?.flexPLMObjectClass){
374
+ flexPayload.data.flexPLMObjectClass = flexPayload.objectClass;
375
+ }
376
+ const inboundData = await this.getTransformedData(flexPayload);
377
+ outboundEntityUpdates = await this.getUpdatesForEntity(event.newData, inboundData)
378
+ }
379
+ return outboundEntityUpdates;
380
+ }
381
+
382
+ async handleOutgoingDelete(entityType, event) {
383
+ console.warn('delete is not configured', entityType, event.oldData);
384
+ }
385
+
386
+ // This method must be implemented by derived classes
387
+ protected abstract getOutgoingUpsertPayload(entityType, event): Promise<EntityPayloadType>;
388
+
389
+ /** Create a new event-workflow-request to rerun sending the entity to FlexPLM
390
+ * The event must contain any information needed to ensure it is put in the correct queue for the entity
391
+ *
392
+ * @param triggerKey Ex: event.entityType + '|sendUpsertToFlexPLM'
393
+ * @param event
394
+ * @returns
395
+ */
396
+
397
+ protected async triggerNewEvent(triggerKey: string, event: any) {
398
+ const newEvent = {
399
+ entityName: 'event-workflow-request',
400
+ object: {
401
+ triggerKey,
402
+ event
403
+ }
404
+ };
405
+ const response = await this.entities.create(newEvent);
406
+
407
+ return response;
408
+ }
409
+
410
+ /** Sends the current state of the entity to FlexPLM.
411
+ * So any changes made in Vibe between the event being generated and the event being processed are sent to FlexPLM.
412
+ *
413
+ * @param event must contain entityType, id; which are used to query for the entity
414
+ * @returns results of sending the entity to FlexPLM
415
+ */
416
+ protected async sendUpsertToFlexPLM(event) {
417
+ const payload = await this.getEntityCurrentStateUpsertPayload(event);
418
+ if(!payload){
419
+ const message = 'No payload to send to FlexPLM';
420
+ console.log(message);
421
+ return {
422
+ status: 500,
423
+ data: {message}
424
+ };
425
+ };
426
+ let objectClass = payload.objectClass;
427
+ try {
428
+ const flexResponse: any = await new FlexPLMConnect(this.config).sendToFlexPLM(payload);
429
+
430
+ const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
431
+ if(outboundEntityUpdates){
432
+ flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
433
+ }
434
+
435
+ const statusMsg = 'BaseEntityProcessor: outbound: status: ' + EventShortMessageStatus.SUCCESS
436
+ + ', statusMessage: ' + flexResponse.status
437
+ + ', entityType: ' + this.baseType
438
+ + ', entityId: ' + event.id
439
+ + ', objectClass: ' + objectClass
440
+ + ', updateFromResponse: ' + ((outboundEntityUpdates &&Object.keys(outboundEntityUpdates).length > 0)? 'true' : 'false')
441
+ + ', orgSlug: ' + this.orgSlug;
442
+ console.log(statusMsg);
443
+ return flexResponse;
444
+ }catch(e){
445
+ const statusMsg = 'BaseEntityProcessor: outbound: status: '+ EventShortMessageStatus.FAILURE
446
+ + ', statusMessage: ' + e.httpResponseStatus
447
+ + ', entityType: ' + this.baseType
448
+ + ', entityId: ' + event.id
449
+ + ', objectClass: ' + objectClass
450
+ + ', updateFromResponse: ' + 'false'
451
+ + ', orgSlug: ' + this.orgSlug;
452
+ console.log(statusMsg);
453
+ throw e;
454
+ }
455
+ }
456
+
457
+ /** Generates the payload to send to FlexPLM, based on the current state of the entity.
458
+ * The current state of the entity are used as the newData and oldData; which is passed
459
+ * to getOutgoingUpsertPayload to generate the payload.
460
+ * @param event information about the item to send to FlexPLM
461
+ * @returns The payload to send to FlexPLM
462
+ */
463
+ protected async getEntityCurrentStateUpsertPayload(event: any): Promise<EntityPayloadType> {
464
+
465
+ const id = event.id;
466
+ if(!id){
467
+ return undefined;
468
+ }
469
+
470
+ const entity = await this.entities.get({
471
+ entityName: this.baseType,
472
+ id
473
+ });
474
+
475
+ if(!entity){
476
+ return undefined;
477
+ }
478
+
479
+ event.newData = entity;
480
+ event.oldData = entity;
481
+
482
+ const payload = await this.getOutgoingUpsertPayload(this.baseType, event);
483
+
484
+ return payload;
485
+ }
486
+ }