@contrail/flexplm 1.2.1 → 1.3.0-alpha.04c91a9

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.
Files changed (125) hide show
  1. package/.github/pull_request_template.md +31 -31
  2. package/.github/workflows/flexplm-lib.yml +27 -27
  3. package/.github/workflows/publish-to-npm.yml +131 -0
  4. package/CHANGELOG.md +10 -0
  5. package/lib/entity-processor/base-entity-processor.d.ts +42 -42
  6. package/lib/entity-processor/base-entity-processor.js +385 -363
  7. package/lib/entity-processor/base-entity-processor.spec.d.ts +1 -1
  8. package/lib/entity-processor/base-entity-processor.spec.js +397 -302
  9. package/lib/flexplm-request.d.ts +3 -3
  10. package/lib/flexplm-request.js +34 -34
  11. package/lib/flexplm-utils.d.ts +5 -5
  12. package/lib/flexplm-utils.js +33 -33
  13. package/lib/flexplm-utils.spec.d.ts +1 -1
  14. package/lib/flexplm-utils.spec.js +26 -26
  15. package/lib/index.d.ts +22 -22
  16. package/lib/index.js +38 -38
  17. package/lib/interfaces/interfaces.d.ts +105 -105
  18. package/lib/interfaces/interfaces.js +2 -2
  19. package/lib/interfaces/item-family-changes.d.ts +20 -20
  20. package/lib/interfaces/item-family-changes.js +56 -56
  21. package/lib/interfaces/publish-change-data.d.ts +19 -19
  22. package/lib/interfaces/publish-change-data.js +32 -32
  23. package/lib/publish/base-process-publish-assortment-callback.d.ts +9 -9
  24. package/lib/publish/base-process-publish-assortment-callback.js +38 -38
  25. package/lib/publish/base-process-publish-assortment.d.ts +93 -93
  26. package/lib/publish/base-process-publish-assortment.js +944 -944
  27. package/lib/publish/base-process-publish-assortment.spec.d.ts +1 -1
  28. package/lib/publish/base-process-publish-assortment.spec.js +1670 -1670
  29. package/lib/publish/mockData.d.ts +1389 -1389
  30. package/lib/publish/mockData.js +4519 -4519
  31. package/lib/transform/identifier-conversion-spec-mockData.js +444 -444
  32. package/lib/transform/identifier-conversion.d.ts +15 -15
  33. package/lib/transform/identifier-conversion.js +212 -212
  34. package/lib/transform/identifier-conversion.spec.d.ts +1 -1
  35. package/lib/transform/identifier-conversion.spec.js +339 -339
  36. package/lib/util/config-defaults.d.ts +8 -8
  37. package/lib/util/config-defaults.js +85 -85
  38. package/lib/util/config-defaults.spec.d.ts +1 -1
  39. package/lib/util/config-defaults.spec.js +293 -293
  40. package/lib/util/data-converter-spec-mockData.js +205 -205
  41. package/lib/util/data-converter.d.ts +39 -39
  42. package/lib/util/data-converter.js +592 -592
  43. package/lib/util/data-converter.spec.d.ts +1 -1
  44. package/lib/util/data-converter.spec.js +904 -904
  45. package/lib/util/error-response-object.d.ts +4 -4
  46. package/lib/util/error-response-object.js +47 -47
  47. package/lib/util/error-response-object.spec.d.ts +1 -1
  48. package/lib/util/error-response-object.spec.js +99 -99
  49. package/lib/util/event-short-message-status.d.ts +19 -18
  50. package/lib/util/event-short-message-status.js +23 -22
  51. package/lib/util/federation.d.ts +15 -15
  52. package/lib/util/federation.js +149 -149
  53. package/lib/util/flexplm-connect.d.ts +22 -18
  54. package/lib/util/flexplm-connect.js +176 -171
  55. package/lib/util/flexplm-connect.spec.d.ts +1 -0
  56. package/lib/util/flexplm-connect.spec.js +88 -0
  57. package/lib/util/logger-config.d.ts +1 -1
  58. package/lib/util/logger-config.js +26 -26
  59. package/lib/util/map-util-spec-mockData.js +205 -205
  60. package/lib/util/map-utils.d.ts +6 -6
  61. package/lib/util/map-utils.js +15 -15
  62. package/lib/util/map-utils.spec.d.ts +1 -1
  63. package/lib/util/map-utils.spec.js +89 -89
  64. package/lib/util/mockData.d.ts +80 -79
  65. package/lib/util/mockData.js +103 -99
  66. package/lib/util/thumbnail-util.d.ts +34 -19
  67. package/lib/util/thumbnail-util.js +215 -114
  68. package/lib/util/thumbnail-util.spec.d.ts +1 -1
  69. package/lib/util/thumbnail-util.spec.js +434 -242
  70. package/lib/util/type-conversion-utils-spec-mockData.js +259 -241
  71. package/lib/util/type-conversion-utils.d.ts +23 -21
  72. package/lib/util/type-conversion-utils.js +265 -223
  73. package/lib/util/type-conversion-utils.spec.d.ts +1 -1
  74. package/lib/util/type-conversion-utils.spec.js +868 -708
  75. package/lib/util/type-defaults.d.ts +16 -16
  76. package/lib/util/type-defaults.js +221 -221
  77. package/lib/util/type-defaults.spec.d.ts +1 -1
  78. package/lib/util/type-defaults.spec.js +516 -516
  79. package/lib/util/type-utils.d.ts +13 -13
  80. package/lib/util/type-utils.js +114 -114
  81. package/lib/util/type-utils.spec.d.ts +1 -1
  82. package/lib/util/type-utils.spec.js +190 -190
  83. package/package.json +1 -1
  84. package/publish.bat +4 -4
  85. package/publish.sh +4 -4
  86. package/src/entity-processor/base-entity-processor.spec.ts +122 -0
  87. package/src/entity-processor/base-entity-processor.ts +31 -2
  88. package/src/flexplm-request.ts +28 -28
  89. package/src/flexplm-utils.spec.ts +27 -27
  90. package/src/flexplm-utils.ts +29 -29
  91. package/src/index.ts +21 -21
  92. package/src/interfaces/item-family-changes.ts +66 -66
  93. package/src/interfaces/publish-change-data.ts +42 -42
  94. package/src/publish/base-process-publish-assortment-callback.ts +50 -50
  95. package/src/transform/identifier-conversion-spec-mockData.ts +495 -495
  96. package/src/transform/identifier-conversion.spec.ts +353 -353
  97. package/src/transform/identifier-conversion.ts +281 -281
  98. package/src/util/config-defaults.spec.ts +350 -350
  99. package/src/util/config-defaults.ts +92 -92
  100. package/src/util/data-converter-spec-mockData.ts +230 -230
  101. package/src/util/error-response-object.spec.ts +115 -115
  102. package/src/util/error-response-object.ts +49 -49
  103. package/src/util/event-short-message-status.ts +1 -0
  104. package/src/util/federation.ts +172 -172
  105. package/src/util/flexplm-connect.spec.ts +132 -0
  106. package/src/util/flexplm-connect.ts +14 -5
  107. package/src/util/logger-config.ts +19 -19
  108. package/src/util/map-util-spec-mockData.ts +230 -230
  109. package/src/util/map-utils.spec.ts +102 -102
  110. package/src/util/map-utils.ts +40 -40
  111. package/src/util/mockData.ts +101 -97
  112. package/src/util/thumbnail-util.spec.ts +239 -0
  113. package/src/util/thumbnail-util.ts +140 -5
  114. package/src/util/type-conversion-utils-spec-mockData.ts +18 -0
  115. package/src/util/type-conversion-utils.spec.ts +184 -0
  116. package/src/util/type-conversion-utils.ts +75 -1
  117. package/src/util/type-defaults.spec.ts +668 -668
  118. package/src/util/type-defaults.ts +280 -280
  119. package/src/util/type-utils.spec.ts +227 -227
  120. package/src/util/type-utils.ts +144 -144
  121. package/tsconfig.json +23 -26
  122. package/tslint.json +57 -57
  123. package/.claude/settings.local.json +0 -8
  124. package/scripts/output.png +0 -0
  125. package/scripts/test-get-request.ts +0 -35
@@ -1,8 +1,15 @@
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 {
12
+ /** The max_thumbnail_size is for limiting the size of the thumbnail being sent to FlexPLM. It is used when checking the size of the auto generated thumbnails (smallViewable, tinyViewable, etc.). */
6
13
  private max_thumbnail_size = 5 * 1_024 * 1_024;
7
14
  private entities: Entities;
8
15
  static NEW_THUMBNAIL_ID = 'NEW_THUMBNAIL_ID';
@@ -54,7 +61,7 @@ export class ThumbnailUtil {
54
61
  return false;
55
62
  }
56
63
 
57
- public async getFileId(primaryViewableId: string): Promise<string> {
64
+ public async getFileId(primaryViewableId: string): Promise<string | undefined> {
58
65
  console.info('ThumbnailUtil.getFileId()-' + primaryViewableId);
59
66
  const sizes = await this.getCustomSizes();
60
67
 
@@ -95,16 +102,16 @@ export class ThumbnailUtil {
95
102
  return fileId;
96
103
  }
97
104
 
98
- async getCustomSizes() {
105
+ async getCustomSizes(): Promise<ContentCustomSize[]> {
99
106
  const customSizes = await this.entities.get({
100
107
  entityName: 'content-custom-size'
101
108
  });
102
- const sizes = [];
109
+ const sizes: ContentCustomSize[] = [];
103
110
  sizes.push(...customSizes);
104
111
  return sizes;
105
112
  }
106
113
 
107
- async getContentEntity(primaryViewableId: any, sizes: any[]) {
114
+ async getContentEntity(primaryViewableId: any, sizes: ContentCustomSize[]) {
108
115
  const relations = sizes.map(s => s.slug);
109
116
  relations.push('primaryFile');
110
117
  const content = await this.entities.get({
@@ -134,4 +141,132 @@ export class ThumbnailUtil {
134
141
  }
135
142
  }
136
143
 
144
+ /** Syncs the thumbnail from FlexPLM to VibeIQ. Handles creating, replacing, or removing
145
+ * the primary viewable content and persists the updates directly to the entity.
146
+ *
147
+ * @param entityId - The ID of the entity to update with thumbnail properties.
148
+ * @param primaryViewableId - The existing primary viewable content ID, if any.
149
+ * @param event - The inbound event containing thumbnail data (NEW_THUMBNAIL_ID / EXISTING_THUMBNAIL_ID).
150
+ * @param entityName - The entity type name (e.g. 'item', 'color') used for API calls.
151
+ * @returns The updated entity, or undefined if no thumbnail changes were needed.
152
+ */
153
+ async syncThumbnailToVibeIQ({ entityId, primaryViewableId, event, entityName }: { entityId: string; primaryViewableId?: string; event: any; entityName: string }): Promise<any> {
154
+ console.debug(`syncThumbnailToVibeIQ: entityId=${entityId}, primaryViewableId=${primaryViewableId}, entityName=${entityName}`);
155
+ const eventData = event.data || {};
156
+ const newThumbnailId = eventData[ThumbnailUtil.NEW_THUMBNAIL_ID];
157
+ const existingThumbnailId = eventData[ThumbnailUtil.EXISTING_THUMBNAIL_ID];
158
+ const thumbnailUrl = newThumbnailId || existingThumbnailId;
159
+
160
+ // Case 1: REMOVE_THUMBNAIL
161
+ if (newThumbnailId === ThumbnailUtil.REMOVE_THUMBNAIL) {
162
+ if (primaryViewableId) {
163
+ await this.entities.delete({ entityName: 'content', id: primaryViewableId });
164
+ }
165
+ const clearUpdates = await this.getClearPrimaryViewableUpdates();
166
+ const updatedEntity = await this.entities.update({ entityName, id: entityId, object: clearUpdates });
167
+ console.debug(`syncThumbnailToVibeIQ: applied clear updates for entityId=${entityId}`);
168
+ return updatedEntity;
169
+ }
170
+
171
+ // Early return if no thumbnail URL
172
+ if (!thumbnailUrl) {
173
+ console.debug(`syncThumbnailToVibeIQ: no thumbnail URL for entityId=${entityId}`);
174
+ return undefined;
175
+ }
176
+
177
+ // Case 2: No existing primaryViewableId — create new content
178
+ if (!primaryViewableId) {
179
+ const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
180
+ await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
181
+ const primaryUpdates = await this.getPrimaryViewableUpdates(content);
182
+ const updatedEntity = await this.entities.update({ entityName, id: entityId, object: primaryUpdates });
183
+ console.debug(`syncThumbnailToVibeIQ: created new content ${content.id} for entityId=${entityId}`);
184
+ return updatedEntity;
185
+ }
186
+
187
+ // Case 3: Has primaryViewableId — check if thumbnail changed
188
+ const primaryViewable = await this.entities.get({ entityName: 'content', id: primaryViewableId });
189
+ if (primaryViewable?.flexplmThumbnailUrl === thumbnailUrl) {
190
+ console.debug(`syncThumbnailToVibeIQ: thumbnail already synced for entityId=${entityId}`);
191
+ return undefined; // Already synced
192
+ }
193
+
194
+ const content = await this.createContentFromFlexPLM(thumbnailUrl, entityId, entityName);
195
+ await this.entities.update({ entityName: 'content', id: content.id, object: { flexplmThumbnailUrl: thumbnailUrl } });
196
+ const primaryUpdates = await this.getPrimaryViewableUpdates(content);
197
+ const updatedEntity = await this.entities.update({ entityName, id: entityId, object: primaryUpdates });
198
+ await this.entities.delete({ entityName: 'content', id: primaryViewableId });
199
+ console.debug(`syncThumbnailToVibeIQ: replaced content ${primaryViewableId} with ${content.id} for entityId=${entityId}`);
200
+ return updatedEntity;
201
+ }
202
+
203
+ private async createContentFromFlexPLM(thumbnailUrl: string, entityId: string, entityName: string): Promise<any> {
204
+ const urlParts = thumbnailUrl.split('/');
205
+ const fileName = urlParts[urlParts.length - 1] || 'thumbnail';
206
+
207
+ const encodedUrl = urlParts.map(part => encodeURIComponent(part)).join('/');
208
+ const flexPLMConnect = new FlexPLMConnect(this.config);
209
+ const response = await flexPLMConnect.getRequest({
210
+ urlPath: encodedUrl,
211
+ includeUrlContext: false,
212
+ returnFullResponse: true,
213
+ }) as Response;
214
+
215
+ const fileBuffer = await response.arrayBuffer();
216
+ const buffer = Buffer.from(fileBuffer);
217
+ const contentTypeHeader = response.headers.get('content-type');
218
+ const contentType = contentTypeHeader ? contentTypeHeader.split(';')[0] : 'application/octet-stream';
219
+
220
+ const contentHolderReference = `${entityName}:${entityId}`;
221
+ const content = await new Content().create({
222
+ fileBuffer: buffer,
223
+ fileName,
224
+ contentType,
225
+ contentHolderReference,
226
+ });
227
+ return content;
228
+ }
229
+
230
+ private async getPrimaryViewableUpdates(content: any): Promise<any> {
231
+ const updates: any = {
232
+ primaryViewableId: content.id,
233
+ contentType: content.contentType,
234
+ fileName: content.fileName,
235
+ primaryFileUrl: content.primaryFileUrl,
236
+ largeViewableDownloadUrl: content.largeViewableUrl || content.primaryFileUrl,
237
+ mediumLargeViewableDownloadUrl: content.mediumLargeViewableUrl || content.primaryFileUrl,
238
+ mediumViewableDownloadUrl: content.mediumViewableUrl || content.primaryFileUrl,
239
+ smallViewableDownloadUrl: content.smallViewableUrl || content.primaryFileUrl,
240
+ tinyViewableDownloadUrl: content.tinyViewableUrl || content.primaryFileUrl,
241
+ };
242
+
243
+ const customSizes = await this.getCustomSizes();
244
+ for (const size of customSizes) {
245
+ updates[`${size.slug}DownloadUrl`] = content[`${size.slug}Url`] || content.primaryFileUrl;
246
+ }
247
+
248
+ return updates;
249
+ }
250
+
251
+ private async getClearPrimaryViewableUpdates(): Promise<any> {
252
+ const updates: any = {
253
+ primaryViewableId: null,
254
+ contentType: null,
255
+ fileName: null,
256
+ primaryFileUrl: null,
257
+ largeViewableDownloadUrl: null,
258
+ mediumLargeViewableDownloadUrl: null,
259
+ mediumViewableDownloadUrl: null,
260
+ smallViewableDownloadUrl: null,
261
+ tinyViewableDownloadUrl: null,
262
+ };
263
+
264
+ const customSizes = await this.getCustomSizes();
265
+ for (const size of customSizes) {
266
+ updates[`${size.slug}DownloadUrl`] = null;
267
+ }
268
+
269
+ return updates;
270
+ }
271
+
137
272
  }
@@ -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
  });
@@ -323,7 +323,7 @@ export class TypeConversionUtils {
323
323
  return TypeDefaults.getDefaultInformationalPropertiesFromObject(object);
324
324
  }
325
325
 
326
- static async getMapKeyFromObject(fileId, mapFileUtil: MapFileUtil, object: any, direction:string): Promise<string> {
326
+ static async getMapKeyFromObject(fileId, mapFileUtil: MapFileUtil, object: any, direction:string): Promise<string | undefined> {
327
327
  const type = this.getObjectType(object);
328
328
  if(fileId){
329
329
  const mappingData = await mapFileUtil.getMappingSection(fileId, 'typeConversion', direction);
@@ -377,6 +377,80 @@ export class TypeConversionUtils {
377
377
  return isOutboundCreatable;
378
378
  }
379
379
 
380
+ /** Takes in a FlexPLM object and determines whether inbound
381
+ * images should be synced. In most cases, the creation owning system
382
+ * will also control image syncing. Defaults to false if no mapping exists.
383
+ * Map file entry in '<mapKey>:syncInboundImages()'
384
+ *
385
+ * @param fileId id for mapFile
386
+ * @param mapFileUtil class to get mapfile
387
+ * @param object FlexPLM object
388
+ * @param context optional context object
389
+ * @returns Promise<boolean>
390
+ */
391
+ static async syncInboundImages(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean> {
392
+
393
+ let syncImages = false;
394
+
395
+ if (!fileId) {
396
+ return syncImages;
397
+ }
398
+
399
+ let mapKey: string | undefined;
400
+ try {
401
+ mapKey = await this.getMapKeyFromObject(fileId, mapFileUtil, object, TypeConversionUtils.FLEX2VIBE_DIRECTION);
402
+ } catch {
403
+ return syncImages;
404
+ }
405
+
406
+ if (!mapKey) {
407
+ return syncImages;
408
+ }
409
+
410
+ const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
411
+ if (mapData && mapData['syncInboundImages']) {
412
+ syncImages = await mapData['syncInboundImages'](object, context);
413
+ }
414
+ return syncImages;
415
+ }
416
+
417
+ /** Takes in a VibeIQ entity object and determines whether outbound
418
+ * images should be synced. In most cases, the creation owning system
419
+ * will also control image syncing. Defaults to true if no mapping exists.
420
+ * Map file entry in '<mapKey>:syncOutboundImages()'
421
+ *
422
+ * @param fileId id for mapFile
423
+ * @param mapFileUtil class to get mapfile
424
+ * @param entity VibeIQ entity
425
+ * @param context optional context object
426
+ * @returns Promise<boolean>
427
+ */
428
+ static async syncOutboundImages(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean> {
429
+
430
+ let syncImages = true;
431
+
432
+ if (!fileId) {
433
+ return syncImages;
434
+ }
435
+
436
+ let mapKey: string | undefined;
437
+ try {
438
+ mapKey = await this.getMapKey(fileId, mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
439
+ } catch {
440
+ return syncImages;
441
+ }
442
+
443
+ if (!mapKey) {
444
+ return syncImages;
445
+ }
446
+
447
+ const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
448
+ if (mapData && mapData['syncOutboundImages']) {
449
+ syncImages = await mapData['syncOutboundImages'](entity, context);
450
+ }
451
+ return syncImages;
452
+ }
453
+
380
454
  static getObjectType(object:any) {
381
455
  let objectType = object['flexPLMObjectClass'];
382
456
  return objectType;