@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
|
@@ -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,6 +1,7 @@
|
|
|
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
|
|
|
5
6
|
export class ThumbnailUtil {
|
|
6
7
|
private max_thumbnail_size = 5 * 1_024 * 1_024;
|
|
@@ -134,4 +135,111 @@ export class ThumbnailUtil {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
async syncThumbnailToVibeIQ({ entityId, primaryViewableId, event, entityName }: { entityId: string; primaryViewableId?: string; event: any; entityName: string }): Promise<any> {
|
|
139
|
+
const eventData = event.data || {};
|
|
140
|
+
const newThumbnailId = eventData[ThumbnailUtil.NEW_THUMBNAIL_ID];
|
|
141
|
+
const existingThumbnailId = eventData[ThumbnailUtil.EXISTING_THUMBNAIL_ID];
|
|
142
|
+
const thumbnailUrl = newThumbnailId || existingThumbnailId;
|
|
143
|
+
|
|
144
|
+
// Case 1: REMOVE_THUMBNAIL
|
|
145
|
+
if (newThumbnailId === ThumbnailUtil.REMOVE_THUMBNAIL) {
|
|
146
|
+
if (primaryViewableId) {
|
|
147
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
148
|
+
}
|
|
149
|
+
return await this.getClearPrimaryViewableUpdates();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Early return if no thumbnail URL
|
|
153
|
+
if (!thumbnailUrl) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Case 2: No existing primaryViewableId — create new content
|
|
158
|
+
if (!primaryViewableId) {
|
|
159
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
160
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
161
|
+
return await this.getPrimaryViewableUpdates(content);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Case 3: Has primaryViewableId — check if thumbnail changed
|
|
165
|
+
const primaryViewable = await this.entities.get({ entityName: 'content', id: primaryViewableId });
|
|
166
|
+
if (primaryViewable?.flexplmThumbnailUrl === thumbnailUrl) {
|
|
167
|
+
return undefined; // Already synced
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
|
|
171
|
+
await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
|
|
172
|
+
const primaryUpdates = await this.getPrimaryViewableUpdates(content);
|
|
173
|
+
await this.entities.delete({ entityName: 'content', id: primaryViewableId });
|
|
174
|
+
return primaryUpdates;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async createContentFromFlexPLM(thumbnailUrl: string, entityId: string, entityName: string): Promise<any> {
|
|
178
|
+
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
179
|
+
const response = await flexPLMConnect.getRequest({
|
|
180
|
+
urlPath: thumbnailUrl,
|
|
181
|
+
includeUrlContext: false,
|
|
182
|
+
returnFullResponse: true,
|
|
183
|
+
}) as Response;
|
|
184
|
+
|
|
185
|
+
const fileBuffer = await response.arrayBuffer();
|
|
186
|
+
const buffer = Buffer.from(fileBuffer);
|
|
187
|
+
const contentTypeHeader = response.headers.get('content-type');
|
|
188
|
+
const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : 'application/octet-stream';
|
|
189
|
+
|
|
190
|
+
const urlParts = thumbnailUrl.split('/');
|
|
191
|
+
const fileName = urlParts[urlParts.length - 1] || 'thumbnail';
|
|
192
|
+
|
|
193
|
+
const contentHolderReference = `${entityName}:${entityId}`;
|
|
194
|
+
const content = await new Content().create({
|
|
195
|
+
fileBuffer: buffer,
|
|
196
|
+
fileName,
|
|
197
|
+
contentType,
|
|
198
|
+
contentHolderReference,
|
|
199
|
+
});
|
|
200
|
+
return content;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async getPrimaryViewableUpdates(content: any): Promise<any> {
|
|
204
|
+
const updates: any = {
|
|
205
|
+
primaryViewableId: content.id,
|
|
206
|
+
contentType: content.contentType,
|
|
207
|
+
fileName: content.fileName,
|
|
208
|
+
primaryFileUrl: content.primaryFileUrl,
|
|
209
|
+
largeViewableDownloadUrl: content.largeViewableUrl || content.primaryFileUrl,
|
|
210
|
+
mediumLargeViewableDownloadUrl: content.mediumLargeViewableUrl || content.primaryFileUrl,
|
|
211
|
+
mediumViewableDownloadUrl: content.mediumViewableUrl || content.primaryFileUrl,
|
|
212
|
+
smallViewableDownloadUrl: content.smallViewableUrl || content.primaryFileUrl,
|
|
213
|
+
tinyViewableDownloadUrl: content.tinyViewableUrl || content.primaryFileUrl,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const customSizes = await this.getCustomSizes();
|
|
217
|
+
for (const size of customSizes) {
|
|
218
|
+
updates[`${size.slug}DownloadUrl`] = content[`${size.slug}Url`] || content.primaryFileUrl;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return updates;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async getClearPrimaryViewableUpdates(): Promise<any> {
|
|
225
|
+
const updates: any = {
|
|
226
|
+
primaryViewableId: null,
|
|
227
|
+
contentType: null,
|
|
228
|
+
fileName: null,
|
|
229
|
+
primaryFileUrl: null,
|
|
230
|
+
largeViewableDownloadUrl: null,
|
|
231
|
+
mediumLargeViewableDownloadUrl: null,
|
|
232
|
+
mediumViewableDownloadUrl: null,
|
|
233
|
+
smallViewableDownloadUrl: null,
|
|
234
|
+
tinyViewableDownloadUrl: null,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const customSizes = await this.getCustomSizes();
|
|
238
|
+
for (const size of customSizes) {
|
|
239
|
+
updates[`${size.slug}DownloadUrl`] = null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return updates;
|
|
243
|
+
}
|
|
244
|
+
|
|
137
245
|
}
|
|
@@ -142,6 +142,12 @@ exports.mapping = {
|
|
|
142
142
|
isOutboundCreatable: (entity, context) => {
|
|
143
143
|
return false;
|
|
144
144
|
},
|
|
145
|
+
syncInboundImages: (entity, context) => {
|
|
146
|
+
return true;
|
|
147
|
+
},
|
|
148
|
+
syncOutboundImages: (entity, context) => {
|
|
149
|
+
return false;
|
|
150
|
+
},
|
|
145
151
|
vibe2flex: {
|
|
146
152
|
transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
|
|
147
153
|
rekey: {
|
|
@@ -175,6 +181,18 @@ exports.mapping = {
|
|
|
175
181
|
}
|
|
176
182
|
return true;
|
|
177
183
|
},
|
|
184
|
+
syncInboundImages: (entity, context) => {
|
|
185
|
+
if (context && context.skipImages) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
},
|
|
190
|
+
syncOutboundImages: (entity, context) => {
|
|
191
|
+
if (context && context.skipImages) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
},
|
|
178
196
|
vibe2flex: {
|
|
179
197
|
transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
|
|
180
198
|
rekey: {
|
|
@@ -781,4 +781,188 @@ describe('conversion-utils', () => {
|
|
|
781
781
|
});
|
|
782
782
|
|
|
783
783
|
});
|
|
784
|
+
|
|
785
|
+
describe('syncInboundImages', () =>{
|
|
786
|
+
const mapFileUtil = new MapFileUtil(new Entities());
|
|
787
|
+
|
|
788
|
+
it('should return true for Revisable Entity\\packaging (mapping entry true)', async () =>{
|
|
789
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
790
|
+
.mockImplementation(async () =>{
|
|
791
|
+
return mapping;
|
|
792
|
+
});
|
|
793
|
+
const obj = {
|
|
794
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
795
|
+
flexPLMTypePath: 'Revisable Entity\\packaging'
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
try{
|
|
799
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
800
|
+
expect(results).toBeTruthy();
|
|
801
|
+
|
|
802
|
+
} finally {
|
|
803
|
+
spy.mockRestore();
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should return true for Revisable Entity\\prefix (mapping entry true)', async () =>{
|
|
808
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
809
|
+
.mockImplementation(async () =>{
|
|
810
|
+
return mapping;
|
|
811
|
+
});
|
|
812
|
+
const obj = {
|
|
813
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
814
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
try{
|
|
818
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
819
|
+
expect(results).toBeTruthy();
|
|
820
|
+
|
|
821
|
+
} finally {
|
|
822
|
+
spy.mockRestore();
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it('should pass context to syncInboundImages function', async () =>{
|
|
827
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
828
|
+
.mockImplementation(async () =>{
|
|
829
|
+
return mapping;
|
|
830
|
+
});
|
|
831
|
+
const obj = {
|
|
832
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
833
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
834
|
+
}
|
|
835
|
+
const context = { skipImages: true };
|
|
836
|
+
|
|
837
|
+
try{
|
|
838
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj, context);
|
|
839
|
+
expect(results).toBeFalsy();
|
|
840
|
+
|
|
841
|
+
} finally {
|
|
842
|
+
spy.mockRestore();
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it('should default to false when no mapping exists', async () =>{
|
|
847
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
848
|
+
.mockImplementation(async () =>{
|
|
849
|
+
return mapping;
|
|
850
|
+
});
|
|
851
|
+
const obj = {
|
|
852
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
853
|
+
flexPLMTypePath: 'Revisable Entity\\catName'
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
try{
|
|
857
|
+
const results = await TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
858
|
+
expect(results).toBeFalsy();
|
|
859
|
+
|
|
860
|
+
} finally {
|
|
861
|
+
spy.mockRestore();
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('should default to false when no fileId', async () =>{
|
|
866
|
+
const obj = {
|
|
867
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
868
|
+
flexPLMTypePath: 'Revisable Entity\\pack'
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const results = await TypeConversionUtils.syncInboundImages('', mapFileUtil, obj);
|
|
872
|
+
expect(results).toBeFalsy();
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
describe('syncOutboundImages', () =>{
|
|
878
|
+
const mapFileUtil = new MapFileUtil(new Entities());
|
|
879
|
+
|
|
880
|
+
it('should return false for custom-entity:pack (mapping entry false)', async () =>{
|
|
881
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
882
|
+
.mockImplementation(async () =>{
|
|
883
|
+
return mapping;
|
|
884
|
+
});
|
|
885
|
+
const entity = {
|
|
886
|
+
entityType: 'custom-entity',
|
|
887
|
+
typePath: 'custom-entity:pack'
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
try{
|
|
891
|
+
const results = await TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
892
|
+
expect(results).toBeFalsy();
|
|
893
|
+
|
|
894
|
+
} finally {
|
|
895
|
+
spy.mockRestore();
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it('should return true for custom-entity:prefix (mapping entry true)', async () =>{
|
|
900
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
901
|
+
.mockImplementation(async () =>{
|
|
902
|
+
return mapping;
|
|
903
|
+
});
|
|
904
|
+
const entity = {
|
|
905
|
+
entityType: 'custom-entity',
|
|
906
|
+
typePath: 'custom-entity:prefix'
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
try{
|
|
910
|
+
const results = await TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
911
|
+
expect(results).toBeTruthy();
|
|
912
|
+
|
|
913
|
+
} finally {
|
|
914
|
+
spy.mockRestore();
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it('should pass context to syncOutboundImages function', async () =>{
|
|
919
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
920
|
+
.mockImplementation(async () =>{
|
|
921
|
+
return mapping;
|
|
922
|
+
});
|
|
923
|
+
const entity = {
|
|
924
|
+
entityType: 'custom-entity',
|
|
925
|
+
typePath: 'custom-entity:prefix'
|
|
926
|
+
}
|
|
927
|
+
const context = { skipImages: true };
|
|
928
|
+
|
|
929
|
+
try{
|
|
930
|
+
const results = await TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity, context);
|
|
931
|
+
expect(results).toBeFalsy();
|
|
932
|
+
|
|
933
|
+
} finally {
|
|
934
|
+
spy.mockRestore();
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it('should default to true when no mapping exists', async () =>{
|
|
939
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
940
|
+
.mockImplementation(async () =>{
|
|
941
|
+
return mapping;
|
|
942
|
+
});
|
|
943
|
+
const entity = {
|
|
944
|
+
entityType: 'custom-entity',
|
|
945
|
+
typePath: 'custom-entity:catName'
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
try{
|
|
949
|
+
const results = await TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
950
|
+
expect(results).toBeTruthy();
|
|
951
|
+
|
|
952
|
+
} finally {
|
|
953
|
+
spy.mockRestore();
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it('should default to true when no fileId', async () =>{
|
|
958
|
+
const entity = {
|
|
959
|
+
entityType: 'custom-entity',
|
|
960
|
+
typePath: 'custom-entity:pack'
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const results = await TypeConversionUtils.syncOutboundImages('', mapFileUtil, entity);
|
|
964
|
+
expect(results).toBeTruthy();
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
});
|
|
784
968
|
});
|
|
@@ -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,6 +378,80 @@ export class TypeConversionUtils {
|
|
|
377
378
|
return isOutboundCreatable;
|
|
378
379
|
}
|
|
379
380
|
|
|
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.
|
|
384
|
+
* Map file entry in '<mapKey>:syncInboundImages()'
|
|
385
|
+
*
|
|
386
|
+
* @param fileId id for mapFile
|
|
387
|
+
* @param mapFileUtil class to get mapfile
|
|
388
|
+
* @param object FlexPLM object
|
|
389
|
+
* @param context optional context object
|
|
390
|
+
* @returns Promise<boolean>
|
|
391
|
+
*/
|
|
392
|
+
static async syncInboundImages(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean> {
|
|
393
|
+
|
|
394
|
+
let syncImages = false;
|
|
395
|
+
|
|
396
|
+
if (!fileId) {
|
|
397
|
+
return syncImages;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let mapKey: string | undefined;
|
|
401
|
+
try {
|
|
402
|
+
mapKey = await this.getMapKeyFromObject(fileId, mapFileUtil, object, TypeConversionUtils.FLEX2VIBE_DIRECTION);
|
|
403
|
+
} catch {
|
|
404
|
+
return syncImages;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!mapKey) {
|
|
408
|
+
return syncImages;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
412
|
+
if (mapData && mapData['syncInboundImages']) {
|
|
413
|
+
syncImages = await mapData['syncInboundImages'](object, context);
|
|
414
|
+
}
|
|
415
|
+
return syncImages;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Takes in a VibeIQ entity object and determines whether outbound
|
|
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.
|
|
421
|
+
* Map file entry in '<mapKey>:syncOutboundImages()'
|
|
422
|
+
*
|
|
423
|
+
* @param fileId id for mapFile
|
|
424
|
+
* @param mapFileUtil class to get mapfile
|
|
425
|
+
* @param entity VibeIQ entity
|
|
426
|
+
* @param context optional context object
|
|
427
|
+
* @returns Promise<boolean>
|
|
428
|
+
*/
|
|
429
|
+
static async syncOutboundImages(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean> {
|
|
430
|
+
|
|
431
|
+
let syncImages = true;
|
|
432
|
+
|
|
433
|
+
if (!fileId) {
|
|
434
|
+
return syncImages;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let mapKey: string | undefined;
|
|
438
|
+
try {
|
|
439
|
+
mapKey = await this.getMapKey(fileId, mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
|
|
440
|
+
} catch {
|
|
441
|
+
return syncImages;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!mapKey) {
|
|
445
|
+
return syncImages;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
449
|
+
if (mapData && mapData['syncOutboundImages']) {
|
|
450
|
+
syncImages = await mapData['syncOutboundImages'](entity, context);
|
|
451
|
+
}
|
|
452
|
+
return syncImages;
|
|
453
|
+
}
|
|
454
|
+
|
|
380
455
|
static getObjectType(object:any) {
|
|
381
456
|
let objectType = object['flexPLMObjectClass'];
|
|
382
457
|
return objectType;
|