@contrail/flexplm 1.3.0-alpha.0 → 1.3.0-alpha.4
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 +2 -1
- package/.github/pull_request_template.md +31 -31
- package/.github/workflows/flexplm-lib.yml +27 -27
- package/CHANGELOG.md +1 -1
- package/lib/entity-processor/base-entity-processor.d.ts +42 -0
- package/lib/entity-processor/base-entity-processor.js +377 -0
- package/lib/entity-processor/base-entity-processor.spec.d.ts +1 -0
- package/lib/entity-processor/base-entity-processor.spec.js +426 -0
- package/lib/flexplm-request.d.ts +3 -0
- package/lib/flexplm-request.js +34 -0
- package/lib/flexplm-utils.d.ts +5 -0
- package/lib/flexplm-utils.js +33 -0
- package/lib/flexplm-utils.spec.d.ts +1 -0
- package/lib/flexplm-utils.spec.js +26 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +38 -0
- package/lib/interfaces/interfaces.d.ts +105 -0
- package/lib/interfaces/interfaces.js +2 -0
- package/lib/interfaces/item-family-changes.d.ts +20 -0
- package/lib/interfaces/item-family-changes.js +56 -0
- package/lib/interfaces/publish-change-data.d.ts +19 -0
- package/lib/interfaces/publish-change-data.js +32 -0
- package/lib/publish/base-process-publish-assortment-callback.d.ts +9 -0
- package/lib/publish/base-process-publish-assortment-callback.js +38 -0
- package/lib/publish/base-process-publish-assortment.d.ts +93 -0
- package/lib/publish/base-process-publish-assortment.js +944 -0
- package/lib/publish/base-process-publish-assortment.spec.d.ts +1 -0
- package/lib/publish/base-process-publish-assortment.spec.js +1670 -0
- package/lib/publish/mockData.d.ts +1389 -0
- package/lib/publish/mockData.js +4519 -0
- package/lib/transform/identifier-conversion-spec-mockData.d.ts +0 -0
- package/lib/transform/identifier-conversion-spec-mockData.js +444 -0
- package/lib/transform/identifier-conversion.d.ts +15 -0
- package/lib/transform/identifier-conversion.js +212 -0
- package/lib/transform/identifier-conversion.spec.d.ts +1 -0
- package/lib/transform/identifier-conversion.spec.js +339 -0
- package/lib/util/config-defaults.d.ts +8 -0
- package/lib/util/config-defaults.js +85 -0
- package/lib/util/config-defaults.spec.d.ts +1 -0
- package/lib/util/config-defaults.spec.js +293 -0
- package/lib/util/data-converter-spec-mockData.d.ts +0 -0
- package/lib/util/data-converter-spec-mockData.js +205 -0
- package/lib/util/data-converter.d.ts +39 -0
- package/lib/util/data-converter.js +592 -0
- package/lib/util/data-converter.spec.d.ts +1 -0
- package/lib/util/data-converter.spec.js +904 -0
- package/lib/util/error-response-object.d.ts +4 -0
- package/lib/util/error-response-object.js +47 -0
- package/lib/util/error-response-object.spec.d.ts +1 -0
- package/lib/util/error-response-object.spec.js +99 -0
- package/lib/util/event-short-message-status.d.ts +18 -0
- package/lib/util/event-short-message-status.js +22 -0
- package/lib/util/federation.d.ts +15 -0
- package/lib/util/federation.js +149 -0
- package/lib/util/flexplm-connect.d.ts +22 -0
- package/lib/util/flexplm-connect.js +176 -0
- package/lib/util/flexplm-connect.spec.d.ts +1 -0
- package/lib/util/flexplm-connect.spec.js +88 -0
- package/lib/util/logger-config.d.ts +1 -0
- package/lib/util/logger-config.js +26 -0
- package/lib/util/map-util-spec-mockData.d.ts +0 -0
- package/lib/util/map-util-spec-mockData.js +205 -0
- package/lib/util/map-utils.d.ts +6 -0
- package/lib/util/map-utils.js +15 -0
- package/lib/util/map-utils.spec.d.ts +1 -0
- package/lib/util/map-utils.spec.js +89 -0
- package/lib/util/mockData.d.ts +80 -0
- package/lib/util/mockData.js +103 -0
- package/lib/util/thumbnail-util.d.ts +34 -0
- package/lib/util/thumbnail-util.js +211 -0
- package/lib/util/thumbnail-util.spec.d.ts +1 -0
- package/lib/util/thumbnail-util.spec.js +398 -0
- package/lib/util/type-conversion-utils-spec-mockData.d.ts +0 -0
- package/lib/util/type-conversion-utils-spec-mockData.js +259 -0
- package/lib/util/type-conversion-utils.d.ts +23 -0
- package/lib/util/type-conversion-utils.js +266 -0
- package/lib/util/type-conversion-utils.spec.d.ts +1 -0
- package/lib/util/type-conversion-utils.spec.js +868 -0
- package/lib/util/type-defaults.d.ts +16 -0
- package/lib/util/type-defaults.js +221 -0
- package/lib/util/type-defaults.spec.d.ts +1 -0
- package/lib/util/type-defaults.spec.js +516 -0
- package/lib/util/type-utils.d.ts +13 -0
- package/lib/util/type-utils.js +114 -0
- package/lib/util/type-utils.spec.d.ts +1 -0
- package/lib/util/type-utils.spec.js +190 -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/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 +101 -97
- package/src/util/thumbnail-util.spec.ts +190 -0
- package/src/util/thumbnail-util.ts +126 -5
- package/src/util/type-conversion-utils.spec.ts +25 -25
- package/src/util/type-conversion-utils.ts +10 -9
- 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
|
@@ -3,6 +3,33 @@ import { ThumbnailUtil } from './thumbnail-util';
|
|
|
3
3
|
|
|
4
4
|
import { empty_custom_sizes, four_custom_sizes, thumbnail_content_entity } from './mockData';
|
|
5
5
|
|
|
6
|
+
const mockEntitiesGet = jest.fn();
|
|
7
|
+
const mockEntitiesUpdate = jest.fn();
|
|
8
|
+
const mockEntitiesDelete = jest.fn();
|
|
9
|
+
const mockContentCreate = jest.fn();
|
|
10
|
+
|
|
11
|
+
jest.mock('@contrail/sdk', () => {
|
|
12
|
+
return {
|
|
13
|
+
Entities: jest.fn().mockImplementation(() => ({
|
|
14
|
+
get: mockEntitiesGet,
|
|
15
|
+
update: mockEntitiesUpdate,
|
|
16
|
+
delete: mockEntitiesDelete,
|
|
17
|
+
})),
|
|
18
|
+
Content: jest.fn().mockImplementation(() => ({
|
|
19
|
+
create: mockContentCreate,
|
|
20
|
+
})),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const mockGetRequest = jest.fn();
|
|
25
|
+
jest.mock('./flexplm-connect', () => {
|
|
26
|
+
return {
|
|
27
|
+
FlexPLMConnect: jest.fn().mockImplementation(() => ({
|
|
28
|
+
getRequest: mockGetRequest,
|
|
29
|
+
})),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
6
33
|
describe('ThumbnailUtil Tests', () =>{
|
|
7
34
|
const config = {} as FCConfig;
|
|
8
35
|
describe('setOutboundThumbnail()', () =>{
|
|
@@ -266,4 +293,167 @@ describe('ThumbnailUtil Tests', () =>{
|
|
|
266
293
|
});
|
|
267
294
|
|
|
268
295
|
});
|
|
296
|
+
|
|
297
|
+
describe('syncThumbnailToVibeIQ', () => {
|
|
298
|
+
let tu: ThumbnailUtil;
|
|
299
|
+
|
|
300
|
+
beforeEach(() => {
|
|
301
|
+
jest.clearAllMocks();
|
|
302
|
+
tu = new ThumbnailUtil(config);
|
|
303
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
304
|
+
if (opts.entityName === 'content-custom-size') return Promise.resolve([]);
|
|
305
|
+
return Promise.resolve({});
|
|
306
|
+
});
|
|
307
|
+
mockEntitiesUpdate.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
308
|
+
mockEntitiesDelete.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('returns undefined when no thumbnail IDs in event data', async () => {
|
|
312
|
+
const event = { data: {} };
|
|
313
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
314
|
+
|
|
315
|
+
expect(result).toBeUndefined();
|
|
316
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
317
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
318
|
+
expect(mockContentCreate).not.toHaveBeenCalled();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('returns undefined when event.data is undefined', async () => {
|
|
322
|
+
const event = {};
|
|
323
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
324
|
+
|
|
325
|
+
expect(result).toBeUndefined();
|
|
326
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
327
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('REMOVE_THUMBNAIL with existing primaryViewableId deletes content and returns clear updates', async () => {
|
|
331
|
+
const event = { data: { [ThumbnailUtil.NEW_THUMBNAIL_ID]: ThumbnailUtil.REMOVE_THUMBNAIL } };
|
|
332
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'pv1', event, entityName: 'color' });
|
|
333
|
+
|
|
334
|
+
expect(mockEntitiesDelete).toHaveBeenCalledWith({ entityName: 'content', id: 'pv1' });
|
|
335
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: null, primaryFileUrl: null }));
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('REMOVE_THUMBNAIL with no primaryViewableId returns clear updates without deleting', async () => {
|
|
339
|
+
const event = { data: { [ThumbnailUtil.NEW_THUMBNAIL_ID]: ThumbnailUtil.REMOVE_THUMBNAIL } };
|
|
340
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'item' });
|
|
341
|
+
|
|
342
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
343
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: null }));
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('creates new content when no primaryViewableId exists and returns primary viewable updates', async () => {
|
|
347
|
+
const mockResponse = {
|
|
348
|
+
arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(8)),
|
|
349
|
+
headers: { get: jest.fn().mockReturnValue('image/png') },
|
|
350
|
+
};
|
|
351
|
+
mockGetRequest.mockResolvedValue(mockResponse);
|
|
352
|
+
|
|
353
|
+
const createdContent = {
|
|
354
|
+
id: 'newContent1',
|
|
355
|
+
contentType: 'image/png',
|
|
356
|
+
fileName: 'thumb.png',
|
|
357
|
+
primaryFileUrl: 'https://files/primary.png',
|
|
358
|
+
largeViewableUrl: 'https://files/large.png',
|
|
359
|
+
mediumLargeViewableUrl: null,
|
|
360
|
+
mediumViewableUrl: null,
|
|
361
|
+
smallViewableUrl: null,
|
|
362
|
+
tinyViewableUrl: null,
|
|
363
|
+
};
|
|
364
|
+
mockContentCreate.mockResolvedValue(createdContent);
|
|
365
|
+
|
|
366
|
+
const event = { data: { [ThumbnailUtil.NEW_THUMBNAIL_ID]: '/rest/thumbnail/thumb.png' } };
|
|
367
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
368
|
+
|
|
369
|
+
expect(mockGetRequest).toHaveBeenCalledWith({ urlPath: '/rest/thumbnail/thumb.png', includeUrlContext: false, returnFullResponse: true });
|
|
370
|
+
expect(mockContentCreate).toHaveBeenCalledWith(
|
|
371
|
+
expect.objectContaining({
|
|
372
|
+
fileName: 'thumb.png',
|
|
373
|
+
contentType: 'image/png',
|
|
374
|
+
contentHolderReference: 'color:entity1',
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
// Updates content with flexplmThumbnailUrl
|
|
378
|
+
expect(mockEntitiesUpdate).toHaveBeenCalledWith(
|
|
379
|
+
expect.objectContaining({
|
|
380
|
+
entityName: 'content',
|
|
381
|
+
id: 'newContent1',
|
|
382
|
+
object: { flexplmThumbnailUrl: '/rest/thumbnail/thumb.png' },
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
// Returns primary viewable updates for the entity
|
|
386
|
+
expect(result).toEqual(expect.objectContaining({
|
|
387
|
+
primaryViewableId: 'newContent1',
|
|
388
|
+
primaryFileUrl: 'https://files/primary.png',
|
|
389
|
+
largeViewableDownloadUrl: 'https://files/large.png',
|
|
390
|
+
}));
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('replaces content when primaryViewable.flexplmThumbnailUrl differs and returns updates', async () => {
|
|
394
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
395
|
+
if (opts.entityName === 'content-custom-size') return Promise.resolve([]);
|
|
396
|
+
if (opts.entityName === 'content' && opts.id === 'oldPv') {
|
|
397
|
+
return Promise.resolve({ id: 'oldPv', flexplmThumbnailUrl: '/rest/thumbnail/old.png' });
|
|
398
|
+
}
|
|
399
|
+
return Promise.resolve({});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const mockResponse = {
|
|
403
|
+
arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(8)),
|
|
404
|
+
headers: { get: jest.fn().mockReturnValue('image/jpeg') },
|
|
405
|
+
};
|
|
406
|
+
mockGetRequest.mockResolvedValue(mockResponse);
|
|
407
|
+
|
|
408
|
+
const createdContent = {
|
|
409
|
+
id: 'newContent2',
|
|
410
|
+
contentType: 'image/jpeg',
|
|
411
|
+
fileName: 'new.jpg',
|
|
412
|
+
primaryFileUrl: 'https://files/new-primary.jpg',
|
|
413
|
+
largeViewableUrl: null,
|
|
414
|
+
mediumLargeViewableUrl: null,
|
|
415
|
+
mediumViewableUrl: null,
|
|
416
|
+
smallViewableUrl: null,
|
|
417
|
+
tinyViewableUrl: null,
|
|
418
|
+
};
|
|
419
|
+
mockContentCreate.mockResolvedValue(createdContent);
|
|
420
|
+
|
|
421
|
+
const event = { data: { [ThumbnailUtil.NEW_THUMBNAIL_ID]: '/rest/thumbnail/new.jpg' } };
|
|
422
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'oldPv', event, entityName: 'item' });
|
|
423
|
+
|
|
424
|
+
// Creates new content
|
|
425
|
+
expect(mockContentCreate).toHaveBeenCalled();
|
|
426
|
+
// Updates new content with flexplmThumbnailUrl
|
|
427
|
+
expect(mockEntitiesUpdate).toHaveBeenCalledWith(
|
|
428
|
+
expect.objectContaining({
|
|
429
|
+
entityName: 'content',
|
|
430
|
+
id: 'newContent2',
|
|
431
|
+
object: { flexplmThumbnailUrl: '/rest/thumbnail/new.jpg' },
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
// Returns primary viewable updates for the entity
|
|
435
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: 'newContent2' }));
|
|
436
|
+
// Deletes old content
|
|
437
|
+
expect(mockEntitiesDelete).toHaveBeenCalledWith({ entityName: 'content', id: 'oldPv' });
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('returns undefined when primaryViewable.flexplmThumbnailUrl matches', async () => {
|
|
441
|
+
const thumbnailUrl = '/rest/thumbnail/same.png';
|
|
442
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
443
|
+
if (opts.entityName === 'content-custom-size') return Promise.resolve([]);
|
|
444
|
+
if (opts.entityName === 'content' && opts.id === 'pv1') {
|
|
445
|
+
return Promise.resolve({ id: 'pv1', flexplmThumbnailUrl: thumbnailUrl });
|
|
446
|
+
}
|
|
447
|
+
return Promise.resolve({});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const event = { data: { [ThumbnailUtil.EXISTING_THUMBNAIL_ID]: thumbnailUrl } };
|
|
451
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'pv1', event, entityName: 'color' });
|
|
452
|
+
|
|
453
|
+
expect(result).toBeUndefined();
|
|
454
|
+
expect(mockContentCreate).not.toHaveBeenCalled();
|
|
455
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
456
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
457
|
+
});
|
|
458
|
+
});
|
|
269
459
|
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Logger } from '@contrail/app-framework';
|
|
2
|
-
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import { Content, Entities } from '@contrail/sdk';
|
|
3
3
|
import { FCConfig } from '../interfaces/interfaces';
|
|
4
|
+
import { FlexPLMConnect } from './flexplm-connect';
|
|
4
5
|
|
|
6
|
+
interface ContentCustomSize {
|
|
7
|
+
id: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
5
11
|
export class ThumbnailUtil {
|
|
6
12
|
private max_thumbnail_size = 5 * 1_024 * 1_024;
|
|
7
13
|
private entities: Entities;
|
|
@@ -54,7 +60,7 @@ export class ThumbnailUtil {
|
|
|
54
60
|
return false;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
public async getFileId(primaryViewableId: string): Promise<string> {
|
|
63
|
+
public async getFileId(primaryViewableId: string): Promise<string | undefined> {
|
|
58
64
|
console.info('ThumbnailUtil.getFileId()-' + primaryViewableId);
|
|
59
65
|
const sizes = await this.getCustomSizes();
|
|
60
66
|
|
|
@@ -95,16 +101,16 @@ export class ThumbnailUtil {
|
|
|
95
101
|
return fileId;
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
async getCustomSizes() {
|
|
104
|
+
async getCustomSizes(): Promise<ContentCustomSize[]> {
|
|
99
105
|
const customSizes = await this.entities.get({
|
|
100
106
|
entityName: 'content-custom-size'
|
|
101
107
|
});
|
|
102
|
-
const sizes = [];
|
|
108
|
+
const sizes: ContentCustomSize[] = [];
|
|
103
109
|
sizes.push(...customSizes);
|
|
104
110
|
return sizes;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
async getContentEntity(primaryViewableId: any, sizes:
|
|
113
|
+
async getContentEntity(primaryViewableId: any, sizes: ContentCustomSize[]) {
|
|
108
114
|
const relations = sizes.map(s => s.slug);
|
|
109
115
|
relations.push('primaryFile');
|
|
110
116
|
const content = await this.entities.get({
|
|
@@ -134,4 +140,119 @@ export class ThumbnailUtil {
|
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
async syncThumbnailToVibeIQ({ entityId, primaryViewableId, event, entityName }: { entityId: string; primaryViewableId?: string; event: any; entityName: string }): Promise<any> {
|
|
144
|
+
console.debug(`syncThumbnailToVibeIQ: entityId=${entityId}, primaryViewableId=${primaryViewableId}, entityName=${entityName}`);
|
|
145
|
+
const eventData = event.data || {};
|
|
146
|
+
const newThumbnailId = eventData[ThumbnailUtil.NEW_THUMBNAIL_ID];
|
|
147
|
+
const existingThumbnailId = eventData[ThumbnailUtil.EXISTING_THUMBNAIL_ID];
|
|
148
|
+
const thumbnailUrl = newThumbnailId || existingThumbnailId;
|
|
149
|
+
|
|
150
|
+
// Case 1: REMOVE_THUMBNAIL
|
|
151
|
+
if (newThumbnailId === ThumbnailUtil.REMOVE_THUMBNAIL) {
|
|
152
|
+
if (primaryViewableId) {
|
|
153
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
154
|
+
}
|
|
155
|
+
const clearUpdates = await this.getClearPrimaryViewableUpdates();
|
|
156
|
+
console.debug(`syncThumbnailToVibeIQ: returning clear updates for entityId=${entityId}`);
|
|
157
|
+
return clearUpdates;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Early return if no thumbnail URL
|
|
161
|
+
if (!thumbnailUrl) {
|
|
162
|
+
console.debug(`syncThumbnailToVibeIQ: no thumbnail URL for entityId=${entityId}`);
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Case 2: No existing primaryViewableId — create new content
|
|
167
|
+
if (!primaryViewableId) {
|
|
168
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
169
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
170
|
+
const primaryUpdates = await this.getPrimaryViewableUpdates(content);
|
|
171
|
+
console.debug(`syncThumbnailToVibeIQ: created new content ${content.id} for entityId=${entityId}`);
|
|
172
|
+
return primaryUpdates;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Case 3: Has primaryViewableId — check if thumbnail changed
|
|
176
|
+
const primaryViewable = await this.entities.get({ entityName: 'content', id: primaryViewableId });
|
|
177
|
+
if (primaryViewable?.flexplmThumbnailUrl === thumbnailUrl) {
|
|
178
|
+
console.debug(`syncThumbnailToVibeIQ: thumbnail already synced for entityId=${entityId}`);
|
|
179
|
+
return undefined; // Already synced
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
183
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
184
|
+
const primaryUpdates = await this.getPrimaryViewableUpdates(content);
|
|
185
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
186
|
+
console.debug(`syncThumbnailToVibeIQ: replaced content ${primaryViewableId} with ${content.id} for entityId=${entityId}`);
|
|
187
|
+
return primaryUpdates;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private async createContentFromFlexPLM(thumbnailUrl: string, entityId: string, entityName: string): Promise<any> {
|
|
191
|
+
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
192
|
+
const response = await flexPLMConnect.getRequest({
|
|
193
|
+
urlPath: thumbnailUrl,
|
|
194
|
+
includeUrlContext: false,
|
|
195
|
+
returnFullResponse: true,
|
|
196
|
+
}) as Response;
|
|
197
|
+
|
|
198
|
+
const fileBuffer = await response.arrayBuffer();
|
|
199
|
+
const buffer = Buffer.from(fileBuffer);
|
|
200
|
+
const contentTypeHeader = response.headers.get('content-type');
|
|
201
|
+
const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : 'application/octet-stream';
|
|
202
|
+
|
|
203
|
+
const urlParts = thumbnailUrl.split('/');
|
|
204
|
+
const fileName = urlParts[urlParts.length - 1] || 'thumbnail';
|
|
205
|
+
|
|
206
|
+
const contentHolderReference = `${entityName}:${entityId}`;
|
|
207
|
+
const content = await new Content().create({
|
|
208
|
+
fileBuffer: buffer,
|
|
209
|
+
fileName,
|
|
210
|
+
contentType,
|
|
211
|
+
contentHolderReference,
|
|
212
|
+
});
|
|
213
|
+
return content;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async getPrimaryViewableUpdates(content: any): Promise<any> {
|
|
217
|
+
const updates: any = {
|
|
218
|
+
primaryViewableId: content.id,
|
|
219
|
+
contentType: content.contentType,
|
|
220
|
+
fileName: content.fileName,
|
|
221
|
+
primaryFileUrl: content.primaryFileUrl,
|
|
222
|
+
largeViewableDownloadUrl: content.largeViewableUrl || content.primaryFileUrl,
|
|
223
|
+
mediumLargeViewableDownloadUrl: content.mediumLargeViewableUrl || content.primaryFileUrl,
|
|
224
|
+
mediumViewableDownloadUrl: content.mediumViewableUrl || content.primaryFileUrl,
|
|
225
|
+
smallViewableDownloadUrl: content.smallViewableUrl || content.primaryFileUrl,
|
|
226
|
+
tinyViewableDownloadUrl: content.tinyViewableUrl || content.primaryFileUrl,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const customSizes = await this.getCustomSizes();
|
|
230
|
+
for (const size of customSizes) {
|
|
231
|
+
updates[`${size.slug}DownloadUrl`] = content[`${size.slug}Url`] || content.primaryFileUrl;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return updates;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private async getClearPrimaryViewableUpdates(): Promise<any> {
|
|
238
|
+
const updates: any = {
|
|
239
|
+
primaryViewableId: null,
|
|
240
|
+
contentType: null,
|
|
241
|
+
fileName: null,
|
|
242
|
+
primaryFileUrl: null,
|
|
243
|
+
largeViewableDownloadUrl: null,
|
|
244
|
+
mediumLargeViewableDownloadUrl: null,
|
|
245
|
+
mediumViewableDownloadUrl: null,
|
|
246
|
+
smallViewableDownloadUrl: null,
|
|
247
|
+
tinyViewableDownloadUrl: null,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const customSizes = await this.getCustomSizes();
|
|
251
|
+
for (const size of customSizes) {
|
|
252
|
+
updates[`${size.slug}DownloadUrl`] = null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return updates;
|
|
256
|
+
}
|
|
257
|
+
|
|
137
258
|
}
|
|
@@ -785,18 +785,18 @@ describe('conversion-utils', () => {
|
|
|
785
785
|
describe('syncInboundImages', () =>{
|
|
786
786
|
const mapFileUtil = new MapFileUtil(new Entities());
|
|
787
787
|
|
|
788
|
-
it('should return true for
|
|
788
|
+
it('should return true for Revisable Entity\\packaging (mapping entry true)', async () =>{
|
|
789
789
|
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
790
790
|
.mockImplementation(async () =>{
|
|
791
791
|
return mapping;
|
|
792
792
|
});
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
793
|
+
const obj = {
|
|
794
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
795
|
+
flexPLMTypePath: 'Revisable Entity\\packaging'
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
try{
|
|
799
|
-
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil,
|
|
799
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
800
800
|
expect(results).toBeTruthy();
|
|
801
801
|
|
|
802
802
|
} finally {
|
|
@@ -804,18 +804,18 @@ describe('conversion-utils', () => {
|
|
|
804
804
|
}
|
|
805
805
|
});
|
|
806
806
|
|
|
807
|
-
it('should return true for
|
|
807
|
+
it('should return true for Revisable Entity\\prefix (mapping entry true)', async () =>{
|
|
808
808
|
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
809
809
|
.mockImplementation(async () =>{
|
|
810
810
|
return mapping;
|
|
811
811
|
});
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
812
|
+
const obj = {
|
|
813
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
814
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
815
815
|
}
|
|
816
816
|
|
|
817
817
|
try{
|
|
818
|
-
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil,
|
|
818
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
819
819
|
expect(results).toBeTruthy();
|
|
820
820
|
|
|
821
821
|
} finally {
|
|
@@ -828,14 +828,14 @@ describe('conversion-utils', () => {
|
|
|
828
828
|
.mockImplementation(async () =>{
|
|
829
829
|
return mapping;
|
|
830
830
|
});
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
831
|
+
const obj = {
|
|
832
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
833
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
834
834
|
}
|
|
835
835
|
const context = { skipImages: true };
|
|
836
836
|
|
|
837
837
|
try{
|
|
838
|
-
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil,
|
|
838
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj, context);
|
|
839
839
|
expect(results).toBeFalsy();
|
|
840
840
|
|
|
841
841
|
} finally {
|
|
@@ -848,13 +848,13 @@ describe('conversion-utils', () => {
|
|
|
848
848
|
.mockImplementation(async () =>{
|
|
849
849
|
return mapping;
|
|
850
850
|
});
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
851
|
+
const obj = {
|
|
852
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
853
|
+
flexPLMTypePath: 'Revisable Entity\\catName'
|
|
854
854
|
}
|
|
855
855
|
|
|
856
856
|
try{
|
|
857
|
-
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil,
|
|
857
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
858
858
|
expect(results).toBeFalsy();
|
|
859
859
|
|
|
860
860
|
} finally {
|
|
@@ -863,12 +863,12 @@ describe('conversion-utils', () => {
|
|
|
863
863
|
});
|
|
864
864
|
|
|
865
865
|
it('should default to false when no fileId', async () =>{
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
866
|
+
const obj = {
|
|
867
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
868
|
+
flexPLMTypePath: 'Revisable Entity\\pack'
|
|
869
869
|
}
|
|
870
870
|
|
|
871
|
-
const results = await TypeConversionUtils.syncInboundImages(
|
|
871
|
+
const results = await TypeConversionUtils.syncInboundImages('', mapFileUtil, obj);
|
|
872
872
|
expect(results).toBeFalsy();
|
|
873
873
|
});
|
|
874
874
|
|
|
@@ -877,7 +877,7 @@ describe('conversion-utils', () => {
|
|
|
877
877
|
describe('syncOutboundImages', () =>{
|
|
878
878
|
const mapFileUtil = new MapFileUtil(new Entities());
|
|
879
879
|
|
|
880
|
-
it('should return false for custom-entity:pack', async () =>{
|
|
880
|
+
it('should return false for custom-entity:pack (mapping entry false)', async () =>{
|
|
881
881
|
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
882
882
|
.mockImplementation(async () =>{
|
|
883
883
|
return mapping;
|
|
@@ -896,7 +896,7 @@ describe('conversion-utils', () => {
|
|
|
896
896
|
}
|
|
897
897
|
});
|
|
898
898
|
|
|
899
|
-
it('should return true for custom-entity:prefix', async () =>{
|
|
899
|
+
it('should return true for custom-entity:prefix (mapping entry true)', async () =>{
|
|
900
900
|
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
901
901
|
.mockImplementation(async () =>{
|
|
902
902
|
return mapping;
|
|
@@ -960,7 +960,7 @@ describe('conversion-utils', () => {
|
|
|
960
960
|
typePath: 'custom-entity:pack'
|
|
961
961
|
}
|
|
962
962
|
|
|
963
|
-
const results = await TypeConversionUtils.syncOutboundImages(
|
|
963
|
+
const results = await TypeConversionUtils.syncOutboundImages('', mapFileUtil, entity);
|
|
964
964
|
expect(results).toBeTruthy();
|
|
965
965
|
});
|
|
966
966
|
|
|
@@ -334,6 +334,7 @@ export class TypeConversionUtils {
|
|
|
334
334
|
return type;
|
|
335
335
|
}
|
|
336
336
|
//TODO use TypeDefaults?
|
|
337
|
+
return '';
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
static async isInboundCreatableFromObject(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean> {
|
|
@@ -377,18 +378,18 @@ export class TypeConversionUtils {
|
|
|
377
378
|
return isOutboundCreatable;
|
|
378
379
|
}
|
|
379
380
|
|
|
380
|
-
/** Takes in a
|
|
381
|
-
* images should be synced.
|
|
382
|
-
*
|
|
381
|
+
/** Takes in a FlexPLM object and determines whether inbound
|
|
382
|
+
* images should be synced. In most cases, the creation owning system
|
|
383
|
+
* will also control image syncing. Defaults to false if no mapping exists.
|
|
383
384
|
* Map file entry in '<mapKey>:syncInboundImages()'
|
|
384
385
|
*
|
|
385
386
|
* @param fileId id for mapFile
|
|
386
387
|
* @param mapFileUtil class to get mapfile
|
|
387
|
-
* @param
|
|
388
|
+
* @param object FlexPLM object
|
|
388
389
|
* @param context optional context object
|
|
389
390
|
* @returns Promise<boolean>
|
|
390
391
|
*/
|
|
391
|
-
static async syncInboundImages(fileId: string, mapFileUtil: MapFileUtil,
|
|
392
|
+
static async syncInboundImages(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean> {
|
|
392
393
|
|
|
393
394
|
let syncImages = false;
|
|
394
395
|
|
|
@@ -398,7 +399,7 @@ export class TypeConversionUtils {
|
|
|
398
399
|
|
|
399
400
|
let mapKey: string | undefined;
|
|
400
401
|
try {
|
|
401
|
-
mapKey = await this.
|
|
402
|
+
mapKey = await this.getMapKeyFromObject(fileId, mapFileUtil, object, TypeConversionUtils.FLEX2VIBE_DIRECTION);
|
|
402
403
|
} catch {
|
|
403
404
|
return syncImages;
|
|
404
405
|
}
|
|
@@ -409,14 +410,14 @@ export class TypeConversionUtils {
|
|
|
409
410
|
|
|
410
411
|
const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
411
412
|
if (mapData && mapData['syncInboundImages']) {
|
|
412
|
-
syncImages = await mapData['syncInboundImages'](
|
|
413
|
+
syncImages = await mapData['syncInboundImages'](object, context);
|
|
413
414
|
}
|
|
414
415
|
return syncImages;
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
/** Takes in a VibeIQ entity object and determines whether outbound
|
|
418
|
-
* images should be synced.
|
|
419
|
-
*
|
|
419
|
+
* images should be synced. In most cases, the creation owning system
|
|
420
|
+
* will also control image syncing. Defaults to true if no mapping exists.
|
|
420
421
|
* Map file entry in '<mapKey>:syncOutboundImages()'
|
|
421
422
|
*
|
|
422
423
|
* @param fileId id for mapFile
|