@contrail/flexplm 1.2.1 → 1.3.0-alpha.3
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/.claude/settings.local.json +1 -2
- package/.github/pull_request_template.md +31 -31
- package/.github/workflows/flexplm-lib.yml +27 -27
- package/lib/entity-processor/base-entity-processor.js +16 -2
- package/lib/entity-processor/base-entity-processor.spec.js +124 -0
- package/lib/util/flexplm-connect.d.ts +5 -1
- package/lib/util/flexplm-connect.js +8 -3
- package/lib/util/flexplm-connect.spec.d.ts +1 -0
- package/lib/util/flexplm-connect.spec.js +88 -0
- package/lib/util/thumbnail-util.d.ts +9 -0
- package/lib/util/thumbnail-util.js +88 -0
- package/lib/util/thumbnail-util.spec.js +156 -0
- package/lib/util/type-conversion-utils-spec-mockData.js +18 -0
- package/lib/util/type-conversion-utils.d.ts +2 -0
- package/lib/util/type-conversion-utils.js +43 -0
- package/lib/util/type-conversion-utils.spec.js +160 -0
- package/package.json +1 -1
- package/publish.bat +4 -4
- package/publish.sh +4 -4
- package/src/entity-processor/base-entity-processor.spec.ts +157 -0
- package/src/entity-processor/base-entity-processor.ts +21 -2
- package/src/flexplm-request.ts +28 -28
- package/src/flexplm-utils.spec.ts +27 -27
- package/src/flexplm-utils.ts +29 -29
- package/src/index.ts +21 -21
- package/src/interfaces/item-family-changes.ts +66 -66
- package/src/interfaces/publish-change-data.ts +42 -42
- package/src/publish/base-process-publish-assortment-callback.ts +50 -50
- package/src/transform/identifier-conversion-spec-mockData.ts +495 -495
- package/src/transform/identifier-conversion.spec.ts +353 -353
- package/src/transform/identifier-conversion.ts +281 -281
- package/src/util/config-defaults.spec.ts +350 -350
- package/src/util/config-defaults.ts +92 -92
- package/src/util/data-converter-spec-mockData.ts +230 -230
- package/src/util/error-response-object.spec.ts +115 -115
- package/src/util/error-response-object.ts +49 -49
- package/src/util/federation.ts +172 -172
- package/src/util/flexplm-connect.spec.ts +132 -0
- package/src/util/flexplm-connect.ts +14 -5
- package/src/util/logger-config.ts +19 -19
- package/src/util/map-util-spec-mockData.ts +230 -230
- package/src/util/map-utils.spec.ts +102 -102
- package/src/util/map-utils.ts +40 -40
- package/src/util/mockData.ts +97 -97
- package/src/util/thumbnail-util.spec.ts +190 -0
- package/src/util/thumbnail-util.ts +109 -1
- package/src/util/type-conversion-utils-spec-mockData.ts +18 -0
- package/src/util/type-conversion-utils.spec.ts +184 -0
- package/src/util/type-conversion-utils.ts +75 -0
- package/src/util/type-defaults.spec.ts +668 -668
- package/src/util/type-defaults.ts +280 -280
- package/src/util/type-utils.spec.ts +227 -227
- package/src/util/type-utils.ts +144 -144
- package/tsconfig.json +28 -26
- package/tslint.json +57 -57
- package/scripts/output.png +0 -0
- package/scripts/test-get-request.ts +0 -35
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
## What
|
|
2
|
-
- [ ] Bugfix
|
|
3
|
-
- [ ] Feature
|
|
4
|
-
- [ ] Enhancement
|
|
5
|
-
|
|
6
|
-
Description:
|
|
7
|
-
<!--
|
|
8
|
-
Describe the functional changes of the PR.
|
|
9
|
-
|
|
10
|
-
If the mechanism of your change are complex, describe them in detail as you
|
|
11
|
-
would if you were explaining them to a team mate.
|
|
12
|
-
|
|
13
|
-
If the PR is simple, there is no need to repeat yourself. A small description
|
|
14
|
-
is sufficient.
|
|
15
|
-
-->
|
|
16
|
-
|
|
17
|
-
## Why
|
|
18
|
-
<!--
|
|
19
|
-
why is this change needed? Same rules as with #What
|
|
20
|
-
-->
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Testing added
|
|
24
|
-
<!--
|
|
25
|
-
Describe the testing you added to this PR
|
|
26
|
-
-->
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Link to Jira ticket(s)
|
|
30
|
-
https://vibe-team.atlassian.net/browse/VIBE-TICKET_NUMBER_HERE
|
|
31
|
-
|
|
1
|
+
## What
|
|
2
|
+
- [ ] Bugfix
|
|
3
|
+
- [ ] Feature
|
|
4
|
+
- [ ] Enhancement
|
|
5
|
+
|
|
6
|
+
Description:
|
|
7
|
+
<!--
|
|
8
|
+
Describe the functional changes of the PR.
|
|
9
|
+
|
|
10
|
+
If the mechanism of your change are complex, describe them in detail as you
|
|
11
|
+
would if you were explaining them to a team mate.
|
|
12
|
+
|
|
13
|
+
If the PR is simple, there is no need to repeat yourself. A small description
|
|
14
|
+
is sufficient.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
## Why
|
|
18
|
+
<!--
|
|
19
|
+
why is this change needed? Same rules as with #What
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Testing added
|
|
24
|
+
<!--
|
|
25
|
+
Describe the testing you added to this PR
|
|
26
|
+
-->
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Link to Jira ticket(s)
|
|
30
|
+
https://vibe-team.atlassian.net/browse/VIBE-TICKET_NUMBER_HERE
|
|
31
|
+
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
name: flexplm-lib-tests
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
workflow_dispatch: # allow running in github actions manually
|
|
5
|
-
jobs:
|
|
6
|
-
test:
|
|
7
|
-
runs-on: ubuntu-24.04
|
|
8
|
-
strategy:
|
|
9
|
-
matrix:
|
|
10
|
-
node-version: [22.14.0]
|
|
11
|
-
steps:
|
|
12
|
-
- name: Node.js
|
|
13
|
-
uses: actions/setup-node@v3
|
|
14
|
-
with:
|
|
15
|
-
node-version: ${{ matrix.node-version }}
|
|
16
|
-
|
|
17
|
-
- uses: actions/checkout@v3
|
|
18
|
-
|
|
19
|
-
- name: NPM Install
|
|
20
|
-
env:
|
|
21
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
22
|
-
run: npm ci
|
|
23
|
-
|
|
24
|
-
- name: Unit Tests -
|
|
25
|
-
env:
|
|
26
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
27
|
-
run: npm test
|
|
1
|
+
name: flexplm-lib-tests
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
workflow_dispatch: # allow running in github actions manually
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-24.04
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
node-version: [22.14.0]
|
|
11
|
+
steps:
|
|
12
|
+
- name: Node.js
|
|
13
|
+
uses: actions/setup-node@v3
|
|
14
|
+
with:
|
|
15
|
+
node-version: ${{ matrix.node-version }}
|
|
16
|
+
|
|
17
|
+
- uses: actions/checkout@v3
|
|
18
|
+
|
|
19
|
+
- name: NPM Install
|
|
20
|
+
env:
|
|
21
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
22
|
+
run: npm ci
|
|
23
|
+
|
|
24
|
+
- name: Unit Tests -
|
|
25
|
+
env:
|
|
26
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
27
|
+
run: npm test
|
|
@@ -6,6 +6,7 @@ const flexplm_connect_1 = require("../util/flexplm-connect");
|
|
|
6
6
|
const map_utils_1 = require("../util/map-utils");
|
|
7
7
|
const sdk_1 = require("@contrail/sdk");
|
|
8
8
|
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
9
|
+
const thumbnail_util_1 = require("../util/thumbnail-util");
|
|
9
10
|
const event_short_message_status_1 = require("../util/event-short-message-status");
|
|
10
11
|
const UNSUPPORTED_TYPE = 'Unsupported eventType.';
|
|
11
12
|
class IncomingEntityResponse {
|
|
@@ -68,6 +69,13 @@ class BaseEntityProcessor {
|
|
|
68
69
|
return createEntityResponse.earlyReturn;
|
|
69
70
|
}
|
|
70
71
|
const createdEntity = await this.createEntity(this.baseType, createEntityResponse.entity);
|
|
72
|
+
const shouldSyncThumbnail = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(this.transformMapFile, this.mapFileUtil, event.data);
|
|
73
|
+
if (shouldSyncThumbnail) {
|
|
74
|
+
const thumbnailUpdates = await new thumbnail_util_1.ThumbnailUtil(this.config).syncThumbnailToVibeIQ({ entityId: createdEntity.id, primaryViewableId: createdEntity.primaryViewableId, event, entityName: this.baseType });
|
|
75
|
+
if (thumbnailUpdates) {
|
|
76
|
+
await this.updateEntity(this.baseType, createdEntity, thumbnailUpdates);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
71
79
|
const statusMsg = this.getInboundStatusMessage({
|
|
72
80
|
status: event_short_message_status_1.EventShortMessageStatus.SUCCESS,
|
|
73
81
|
statusMessage: event_short_message_status_1.EventShortMessageStatus.CREATED,
|
|
@@ -79,7 +87,13 @@ class BaseEntityProcessor {
|
|
|
79
87
|
return createdEntity;
|
|
80
88
|
}
|
|
81
89
|
const diffs = await this.getUpdatesForEntity(entity, inboundData);
|
|
82
|
-
|
|
90
|
+
const shouldSyncThumbnail = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(this.transformMapFile, this.mapFileUtil, event.data);
|
|
91
|
+
let thumbnailUpdates;
|
|
92
|
+
if (shouldSyncThumbnail) {
|
|
93
|
+
thumbnailUpdates = await new thumbnail_util_1.ThumbnailUtil(this.config).syncThumbnailToVibeIQ({ entityId: entity.id, primaryViewableId: entity.primaryViewableId, event, entityName: this.baseType });
|
|
94
|
+
}
|
|
95
|
+
const allUpdates = { ...diffs, ...thumbnailUpdates };
|
|
96
|
+
if (Object.getOwnPropertyNames(allUpdates).length == 0) {
|
|
83
97
|
const statusMsg = this.getInboundStatusMessage({
|
|
84
98
|
status: event_short_message_status_1.EventShortMessageStatus.SUCCESS,
|
|
85
99
|
statusMessage: event_short_message_status_1.EventShortMessageStatus.NO_CHANGES,
|
|
@@ -94,7 +108,7 @@ class BaseEntityProcessor {
|
|
|
94
108
|
data: { message }
|
|
95
109
|
};
|
|
96
110
|
}
|
|
97
|
-
const updatedEntity = await this.updateEntity(this.baseType, entity,
|
|
111
|
+
const updatedEntity = await this.updateEntity(this.baseType, entity, allUpdates);
|
|
98
112
|
const statusMsg = this.getInboundStatusMessage({
|
|
99
113
|
status: event_short_message_status_1.EventShortMessageStatus.SUCCESS,
|
|
100
114
|
statusMessage: event_short_message_status_1.EventShortMessageStatus.UPDATED,
|
|
@@ -4,6 +4,8 @@ const sdk_1 = require("@contrail/sdk");
|
|
|
4
4
|
const transform_data_1 = require("@contrail/transform-data");
|
|
5
5
|
const data_converter_1 = require("../util/data-converter");
|
|
6
6
|
const base_entity_processor_1 = require("./base-entity-processor");
|
|
7
|
+
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
8
|
+
const thumbnail_util_1 = require("../util/thumbnail-util");
|
|
7
9
|
const mockRootType = {
|
|
8
10
|
typeProperties: [
|
|
9
11
|
{ slug: 'rootText' }
|
|
@@ -299,4 +301,126 @@ describe('BaseEntityProcessor', () => {
|
|
|
299
301
|
expect(keys).toEqual(['a', 'b']);
|
|
300
302
|
});
|
|
301
303
|
});
|
|
304
|
+
describe('handleIncomingUpsert - inbound image sync', () => {
|
|
305
|
+
const config = {};
|
|
306
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
307
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
308
|
+
const mockEvent = { objectClass: 'TestClass', federatedId: 'fed-123', data: { name: 'test' }, entityReference: 'ref-1', eventType: 'PERSIST' };
|
|
309
|
+
const mockInboundData = { name: 'transformed-test' };
|
|
310
|
+
const mockCreatedEntity = { id: 'created-1', name: 'created', primaryViewableId: 'pv-created' };
|
|
311
|
+
const mockUpdatedEntity = { id: 'updated-1', name: 'updated', primaryViewableId: 'pv-updated' };
|
|
312
|
+
const mockExistingEntity = { id: 'existing-1', name: 'existing', typeId: 'type-1', roles: [] };
|
|
313
|
+
let btep;
|
|
314
|
+
let syncInboundImagesSpy;
|
|
315
|
+
let syncThumbnailSpy;
|
|
316
|
+
beforeEach(() => {
|
|
317
|
+
jest.clearAllMocks();
|
|
318
|
+
btep = new TestBaseEntityProcessor(config, dc, mapFileUtil, 'test');
|
|
319
|
+
jest.spyOn(btep, 'getTransformedData').mockResolvedValue(mockInboundData);
|
|
320
|
+
syncInboundImagesSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'syncInboundImages');
|
|
321
|
+
syncThumbnailSpy = jest.spyOn(thumbnail_util_1.ThumbnailUtil.prototype, 'syncThumbnailToVibeIQ').mockResolvedValue(undefined);
|
|
322
|
+
});
|
|
323
|
+
it('should call updateEntity with thumbnail updates after create when syncInboundImages returns true', async () => {
|
|
324
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: null });
|
|
325
|
+
jest.spyOn(btep, 'getCreateEntity').mockResolvedValue({ entity: { name: 'new' } });
|
|
326
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
327
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockCreatedEntity);
|
|
328
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
329
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new', primaryFileUrl: 'https://files/new.png' };
|
|
330
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
331
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
332
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
333
|
+
expect(syncThumbnailSpy).toBeCalledWith({ entityId: 'created-1', primaryViewableId: 'pv-created', event: mockEvent, entityName: 'test' });
|
|
334
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockCreatedEntity, thumbnailUpdates);
|
|
335
|
+
expect(result).toBe(mockCreatedEntity);
|
|
336
|
+
});
|
|
337
|
+
it('should not call updateEntity after create when syncThumbnailToVibeIQ returns undefined', async () => {
|
|
338
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: null });
|
|
339
|
+
jest.spyOn(btep, 'getCreateEntity').mockResolvedValue({ entity: { name: 'new' } });
|
|
340
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
341
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity');
|
|
342
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
343
|
+
syncThumbnailSpy.mockResolvedValue(undefined);
|
|
344
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
345
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
346
|
+
expect(updateEntitySpy).not.toHaveBeenCalled();
|
|
347
|
+
expect(result).toBe(mockCreatedEntity);
|
|
348
|
+
});
|
|
349
|
+
it('should not sync images after create when syncInboundImages returns false', async () => {
|
|
350
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: null });
|
|
351
|
+
jest.spyOn(btep, 'getCreateEntity').mockResolvedValue({ entity: { name: 'new' } });
|
|
352
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
353
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
354
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
355
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(1);
|
|
356
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
357
|
+
expect(result).toBe(mockCreatedEntity);
|
|
358
|
+
});
|
|
359
|
+
it('should merge thumbnail updates with property diffs on update', async () => {
|
|
360
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: mockExistingEntity });
|
|
361
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({ name: 'changed' });
|
|
362
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
363
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
364
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new' };
|
|
365
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
366
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
367
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
368
|
+
expect(syncThumbnailSpy).toBeCalledWith({ entityId: 'existing-1', primaryViewableId: undefined, event: mockEvent, entityName: 'test' });
|
|
369
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockExistingEntity, { name: 'changed', primaryViewableId: 'pv-new' });
|
|
370
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
371
|
+
});
|
|
372
|
+
it('should update entity with only thumbnail changes when no property diffs', async () => {
|
|
373
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: mockExistingEntity });
|
|
374
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
375
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
376
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
377
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new' };
|
|
378
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
379
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
380
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockExistingEntity, { primaryViewableId: 'pv-new' });
|
|
381
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
382
|
+
});
|
|
383
|
+
it('should not sync images on update when syncInboundImages returns false', async () => {
|
|
384
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: mockExistingEntity });
|
|
385
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({ name: 'changed' });
|
|
386
|
+
jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
387
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
388
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
389
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(1);
|
|
390
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
391
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
392
|
+
});
|
|
393
|
+
it('should not sync images on early return from getIncomingEntity', async () => {
|
|
394
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({
|
|
395
|
+
earlyReturn: { status: 400, data: { message: 'error' }, shortStatusMessage: 'FAIL' }
|
|
396
|
+
});
|
|
397
|
+
await btep.handleIncomingUpsert(mockEvent);
|
|
398
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(0);
|
|
399
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
400
|
+
});
|
|
401
|
+
it('should not sync images on early return from getCreateEntity', async () => {
|
|
402
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: null });
|
|
403
|
+
jest.spyOn(btep, 'getCreateEntity').mockResolvedValue({
|
|
404
|
+
earlyReturn: { status: 400, data: { message: 'not creatable' }, shortStatusMessage: 'NOT_CREATABLE' }
|
|
405
|
+
});
|
|
406
|
+
await btep.handleIncomingUpsert(mockEvent);
|
|
407
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(0);
|
|
408
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
409
|
+
});
|
|
410
|
+
it('should return no changes when no property diffs and no thumbnail updates', async () => {
|
|
411
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: mockExistingEntity });
|
|
412
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
413
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
414
|
+
syncThumbnailSpy.mockResolvedValue(undefined);
|
|
415
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
416
|
+
expect(result).toEqual({ status: 200, data: { message: 'No Changes to persist for entity: existing-1' } });
|
|
417
|
+
});
|
|
418
|
+
it('should return no changes when syncInboundImages is false and no property diffs', async () => {
|
|
419
|
+
jest.spyOn(btep, 'getIncomingEntity').mockResolvedValue({ entity: mockExistingEntity });
|
|
420
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
421
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
422
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
423
|
+
expect(result).toEqual({ status: 200, data: { message: 'No Changes to persist for entity: existing-1' } });
|
|
424
|
+
});
|
|
425
|
+
});
|
|
302
426
|
});
|
|
@@ -14,5 +14,9 @@ export declare class FlexPLMConnect {
|
|
|
14
14
|
protected processRequest(payload: any): Promise<FlexPLMResponseData>;
|
|
15
15
|
sendToFlexPLM(payload: PayloadType): Promise<FlexPLMResponseData>;
|
|
16
16
|
sendMultipleToFlexPLM(payload: PayloadType[]): Promise<FlexPLMResponseData>;
|
|
17
|
-
getRequest(
|
|
17
|
+
getRequest(params?: {
|
|
18
|
+
urlPath?: string;
|
|
19
|
+
includeUrlContext?: boolean;
|
|
20
|
+
returnFullResponse?: boolean;
|
|
21
|
+
}): Promise<Response | unknown>;
|
|
18
22
|
}
|
|
@@ -145,9 +145,11 @@ class FlexPLMConnect {
|
|
|
145
145
|
async sendMultipleToFlexPLM(payload) {
|
|
146
146
|
return await this.processRequest(payload);
|
|
147
147
|
}
|
|
148
|
-
async getRequest() {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
148
|
+
async getRequest(params) {
|
|
149
|
+
const { urlPath, includeUrlContext = true, returnFullResponse = false } = params || {};
|
|
150
|
+
const urlContext = includeUrlContext ? this.config.urlContext : '';
|
|
151
|
+
const path = urlPath || ('/servlet/rest' + this.vibeEventEndpoint);
|
|
152
|
+
const vibeEventsURL = this.config.apiHost + urlContext + path;
|
|
151
153
|
const csrfOptions = this.getRequestOptions('GET');
|
|
152
154
|
const response = await fetch(vibeEventsURL, csrfOptions);
|
|
153
155
|
if (response.status >= 300) {
|
|
@@ -156,6 +158,9 @@ class FlexPLMConnect {
|
|
|
156
158
|
console.error(await response.text());
|
|
157
159
|
throw new Error(message);
|
|
158
160
|
}
|
|
161
|
+
if (returnFullResponse) {
|
|
162
|
+
return response;
|
|
163
|
+
}
|
|
159
164
|
try {
|
|
160
165
|
const data = await response.json();
|
|
161
166
|
return data;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const flexplm_connect_1 = require("./flexplm-connect");
|
|
4
|
+
const mockJsonData = { items: [{ id: '123', name: 'test' }] };
|
|
5
|
+
const mockResponse = (status, json = mockJsonData) => ({
|
|
6
|
+
status,
|
|
7
|
+
json: jest.fn().mockResolvedValue(json),
|
|
8
|
+
text: jest.fn().mockResolvedValue('error text'),
|
|
9
|
+
});
|
|
10
|
+
const globalFetch = global.fetch;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
global.fetch = jest.fn().mockResolvedValue(mockResponse(200));
|
|
13
|
+
});
|
|
14
|
+
afterAll(() => {
|
|
15
|
+
global.fetch = globalFetch;
|
|
16
|
+
});
|
|
17
|
+
const createConfig = (overrides) => ({
|
|
18
|
+
apiHost: 'https://flexplm.example.com',
|
|
19
|
+
urlContext: '/FlexPLM',
|
|
20
|
+
vibeEventEndpoint: '/api/events',
|
|
21
|
+
csrfEndpoint: '/csrf',
|
|
22
|
+
plmEnviornment: 'test',
|
|
23
|
+
userName: () => 'user',
|
|
24
|
+
password: () => 'pass',
|
|
25
|
+
itemPreDevelopmentLifecycleStages: [],
|
|
26
|
+
payloadDefaultAsArray: true,
|
|
27
|
+
...overrides,
|
|
28
|
+
});
|
|
29
|
+
describe('FlexPLMConnect.getRequest', () => {
|
|
30
|
+
it('should call default URL with urlContext when no params provided', async () => {
|
|
31
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
32
|
+
await connect.getRequest();
|
|
33
|
+
expect(global.fetch).toHaveBeenCalledWith('https://flexplm.example.com/FlexPLM/servlet/rest/api/events', expect.objectContaining({ method: 'GET' }));
|
|
34
|
+
});
|
|
35
|
+
it('should use the constructor endpoint in the default URL', async () => {
|
|
36
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig(), '/custom/endpoint');
|
|
37
|
+
await connect.getRequest();
|
|
38
|
+
expect(global.fetch).toHaveBeenCalledWith('https://flexplm.example.com/FlexPLM/servlet/rest/custom/endpoint', expect.any(Object));
|
|
39
|
+
});
|
|
40
|
+
it('should return parsed JSON by default', async () => {
|
|
41
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
42
|
+
const result = await connect.getRequest();
|
|
43
|
+
expect(result).toEqual(mockJsonData);
|
|
44
|
+
});
|
|
45
|
+
it('should use urlPath when provided', async () => {
|
|
46
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
47
|
+
await connect.getRequest({ urlPath: '/custom/path' });
|
|
48
|
+
expect(global.fetch).toHaveBeenCalledWith('https://flexplm.example.com/FlexPLM/custom/path', expect.any(Object));
|
|
49
|
+
});
|
|
50
|
+
it('should exclude urlContext when includeUrlContext is false', async () => {
|
|
51
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
52
|
+
await connect.getRequest({ includeUrlContext: false });
|
|
53
|
+
expect(global.fetch).toHaveBeenCalledWith('https://flexplm.example.com/servlet/rest/api/events', expect.any(Object));
|
|
54
|
+
});
|
|
55
|
+
it('should exclude urlContext with custom urlPath', async () => {
|
|
56
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
57
|
+
await connect.getRequest({ urlPath: '/custom/path', includeUrlContext: false });
|
|
58
|
+
expect(global.fetch).toHaveBeenCalledWith('https://flexplm.example.com/custom/path', expect.any(Object));
|
|
59
|
+
});
|
|
60
|
+
it('should return full response when returnFullResponse is true', async () => {
|
|
61
|
+
const rawResponse = mockResponse(200);
|
|
62
|
+
global.fetch.mockResolvedValue(rawResponse);
|
|
63
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
64
|
+
const result = await connect.getRequest({ returnFullResponse: true });
|
|
65
|
+
expect(result).toBe(rawResponse);
|
|
66
|
+
expect(rawResponse.json).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('should throw on status >= 300', async () => {
|
|
69
|
+
global.fetch.mockResolvedValue(mockResponse(404));
|
|
70
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
71
|
+
await expect(connect.getRequest()).rejects.toThrow('Error connecting to FlexPLM:status: 404');
|
|
72
|
+
});
|
|
73
|
+
it('should throw on status >= 300 even with returnFullResponse', async () => {
|
|
74
|
+
global.fetch.mockResolvedValue(mockResponse(500));
|
|
75
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
76
|
+
await expect(connect.getRequest({ returnFullResponse: true })).rejects.toThrow('Error connecting to FlexPLM:status: 500');
|
|
77
|
+
});
|
|
78
|
+
it('should throw when response.json() fails', async () => {
|
|
79
|
+
const badResponse = {
|
|
80
|
+
status: 200,
|
|
81
|
+
json: jest.fn().mockRejectedValue(new Error('Invalid JSON')),
|
|
82
|
+
text: jest.fn().mockResolvedValue('not json'),
|
|
83
|
+
};
|
|
84
|
+
global.fetch.mockResolvedValue(badResponse);
|
|
85
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
86
|
+
await expect(connect.getRequest()).rejects.toThrow('Error getting json data from FlexPLM: Invalid JSON');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -16,4 +16,13 @@ export declare class ThumbnailUtil {
|
|
|
16
16
|
getCustomSizes(): Promise<any[]>;
|
|
17
17
|
getContentEntity(primaryViewableId: any, sizes: any[]): Promise<any>;
|
|
18
18
|
logContentResults(content: any, relations: string[]): void;
|
|
19
|
+
syncThumbnailToVibeIQ({ entityId, primaryViewableId, event, entityName }: {
|
|
20
|
+
entityId: string;
|
|
21
|
+
primaryViewableId?: string;
|
|
22
|
+
event: any;
|
|
23
|
+
entityName: string;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
private createContentFromFlexPLM;
|
|
26
|
+
private getPrimaryViewableUpdates;
|
|
27
|
+
private getClearPrimaryViewableUpdates;
|
|
19
28
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ThumbnailUtil = void 0;
|
|
4
4
|
const app_framework_1 = require("@contrail/app-framework");
|
|
5
5
|
const sdk_1 = require("@contrail/sdk");
|
|
6
|
+
const flexplm_connect_1 = require("./flexplm-connect");
|
|
6
7
|
class ThumbnailUtil {
|
|
7
8
|
constructor(config) {
|
|
8
9
|
this.config = config;
|
|
@@ -106,6 +107,93 @@ class ThumbnailUtil {
|
|
|
106
107
|
console.debug('content: ' + JSON.stringify(contentCopy));
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
async syncThumbnailToVibeIQ({ entityId, primaryViewableId, event, entityName }) {
|
|
111
|
+
const eventData = event.data || {};
|
|
112
|
+
const newThumbnailId = eventData[ThumbnailUtil.NEW_THUMBNAIL_ID];
|
|
113
|
+
const existingThumbnailId = eventData[ThumbnailUtil.EXISTING_THUMBNAIL_ID];
|
|
114
|
+
const thumbnailUrl = newThumbnailId || existingThumbnailId;
|
|
115
|
+
if (newThumbnailId === ThumbnailUtil.REMOVE_THUMBNAIL) {
|
|
116
|
+
if (primaryViewableId) {
|
|
117
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
118
|
+
}
|
|
119
|
+
return await this.getClearPrimaryViewableUpdates();
|
|
120
|
+
}
|
|
121
|
+
if (!thumbnailUrl) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (!primaryViewableId) {
|
|
125
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
126
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
127
|
+
return await this.getPrimaryViewableUpdates(content);
|
|
128
|
+
}
|
|
129
|
+
const primaryViewable = await this.entities.get({ entityName: 'content', id: primaryViewableId });
|
|
130
|
+
if (primaryViewable?.flexplmThumbnailUrl === thumbnailUrl) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
134
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
135
|
+
const primaryUpdates = await this.getPrimaryViewableUpdates(content);
|
|
136
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
137
|
+
return primaryUpdates;
|
|
138
|
+
}
|
|
139
|
+
async createContentFromFlexPLM(thumbnailUrl, entityId, entityName) {
|
|
140
|
+
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
141
|
+
const response = await flexPLMConnect.getRequest({
|
|
142
|
+
urlPath: thumbnailUrl,
|
|
143
|
+
includeUrlContext: false,
|
|
144
|
+
returnFullResponse: true,
|
|
145
|
+
});
|
|
146
|
+
const fileBuffer = await response.arrayBuffer();
|
|
147
|
+
const buffer = Buffer.from(fileBuffer);
|
|
148
|
+
const contentTypeHeader = response.headers.get('content-type');
|
|
149
|
+
const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : 'application/octet-stream';
|
|
150
|
+
const urlParts = thumbnailUrl.split('/');
|
|
151
|
+
const fileName = urlParts[urlParts.length - 1] || 'thumbnail';
|
|
152
|
+
const contentHolderReference = `${entityName}:${entityId}`;
|
|
153
|
+
const content = await new sdk_1.Content().create({
|
|
154
|
+
fileBuffer: buffer,
|
|
155
|
+
fileName,
|
|
156
|
+
contentType,
|
|
157
|
+
contentHolderReference,
|
|
158
|
+
});
|
|
159
|
+
return content;
|
|
160
|
+
}
|
|
161
|
+
async getPrimaryViewableUpdates(content) {
|
|
162
|
+
const updates = {
|
|
163
|
+
primaryViewableId: content.id,
|
|
164
|
+
contentType: content.contentType,
|
|
165
|
+
fileName: content.fileName,
|
|
166
|
+
primaryFileUrl: content.primaryFileUrl,
|
|
167
|
+
largeViewableDownloadUrl: content.largeViewableUrl || content.primaryFileUrl,
|
|
168
|
+
mediumLargeViewableDownloadUrl: content.mediumLargeViewableUrl || content.primaryFileUrl,
|
|
169
|
+
mediumViewableDownloadUrl: content.mediumViewableUrl || content.primaryFileUrl,
|
|
170
|
+
smallViewableDownloadUrl: content.smallViewableUrl || content.primaryFileUrl,
|
|
171
|
+
tinyViewableDownloadUrl: content.tinyViewableUrl || content.primaryFileUrl,
|
|
172
|
+
};
|
|
173
|
+
const customSizes = await this.getCustomSizes();
|
|
174
|
+
for (const size of customSizes) {
|
|
175
|
+
updates[`${size.slug}DownloadUrl`] = content[`${size.slug}Url`] || content.primaryFileUrl;
|
|
176
|
+
}
|
|
177
|
+
return updates;
|
|
178
|
+
}
|
|
179
|
+
async getClearPrimaryViewableUpdates() {
|
|
180
|
+
const updates = {
|
|
181
|
+
primaryViewableId: null,
|
|
182
|
+
contentType: null,
|
|
183
|
+
fileName: null,
|
|
184
|
+
primaryFileUrl: null,
|
|
185
|
+
largeViewableDownloadUrl: null,
|
|
186
|
+
mediumLargeViewableDownloadUrl: null,
|
|
187
|
+
mediumViewableDownloadUrl: null,
|
|
188
|
+
smallViewableDownloadUrl: null,
|
|
189
|
+
tinyViewableDownloadUrl: null,
|
|
190
|
+
};
|
|
191
|
+
const customSizes = await this.getCustomSizes();
|
|
192
|
+
for (const size of customSizes) {
|
|
193
|
+
updates[`${size.slug}DownloadUrl`] = null;
|
|
194
|
+
}
|
|
195
|
+
return updates;
|
|
196
|
+
}
|
|
109
197
|
}
|
|
110
198
|
exports.ThumbnailUtil = ThumbnailUtil;
|
|
111
199
|
ThumbnailUtil.NEW_THUMBNAIL_ID = 'NEW_THUMBNAIL_ID';
|