@contrail/flexplm 1.1.40 → 1.1.42

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.
@@ -31,4 +31,7 @@ export declare abstract class BaseEntityProcessor {
31
31
  getOutboundEntityUpdates(event: any, flexResponse: any): Promise<any>;
32
32
  handleOutgoingDelete(entityType: any, event: any): Promise<void>;
33
33
  protected abstract getOutgoingUpsertPayload(entityType: any, event: any): Promise<EntityPayloadType>;
34
+ protected triggerNewEvent(triggerKey: string, event: any): Promise<any>;
35
+ protected sendUpsertToFlexPLM(event: any): Promise<any>;
36
+ protected getEntityCurrentStateUpsertPayload(event: any): Promise<EntityPayloadType>;
34
37
  }
@@ -133,6 +133,8 @@ class BaseEntityProcessor {
133
133
  return await this.handleOutgoingUpsert(entityType, event);
134
134
  case 'delete':
135
135
  return await this.handleOutgoingDelete(entityType, event);
136
+ case 'sendUpsertToFlexPLM':
137
+ return await this.sendUpsertToFlexPLM(event);
136
138
  default:
137
139
  console.log(UNSUPPORTED_TYPE);
138
140
  return {
@@ -171,5 +173,51 @@ class BaseEntityProcessor {
171
173
  async handleOutgoingDelete(entityType, event) {
172
174
  console.warn('delete is not configured', entityType, event.oldData);
173
175
  }
176
+ async triggerNewEvent(triggerKey, event) {
177
+ const newEvent = {
178
+ entityName: 'event-workflow-request',
179
+ object: {
180
+ triggerKey,
181
+ event
182
+ }
183
+ };
184
+ const response = await this.entities.create(newEvent);
185
+ return response;
186
+ }
187
+ async sendUpsertToFlexPLM(event) {
188
+ const payload = await this.getEntityCurrentStateUpsertPayload(event);
189
+ if (!payload) {
190
+ const message = 'No payload to send to FlexPLM';
191
+ console.log(message);
192
+ return {
193
+ status: 500,
194
+ data: { message }
195
+ };
196
+ }
197
+ ;
198
+ const flexResponse = await new flexplm_connect_1.FlexPLMConnect(this.config).sendToFlexPLM(payload);
199
+ const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
200
+ if (outboundEntityUpdates) {
201
+ flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
202
+ }
203
+ return flexResponse;
204
+ }
205
+ async getEntityCurrentStateUpsertPayload(event) {
206
+ const id = event.id;
207
+ if (!id) {
208
+ return undefined;
209
+ }
210
+ const entity = await this.entities.get({
211
+ entityName: this.baseType,
212
+ id
213
+ });
214
+ if (!entity) {
215
+ return undefined;
216
+ }
217
+ event.newData = entity;
218
+ event.oldData = entity;
219
+ const payload = await this.getOutgoingUpsertPayload(this.baseType, event);
220
+ return payload;
221
+ }
174
222
  }
175
223
  exports.BaseEntityProcessor = BaseEntityProcessor;
@@ -25,6 +25,7 @@ export declare class BaseProcessPublishAssortment {
25
25
  skip_await?: undefined;
26
26
  }>;
27
27
  getPublishInfo(assortmentId: string, assortmentPublishChangeId: string, apcHistory: any, publisher: any): Promise<any>;
28
+ getSnapshotVersion(assortment: any, apc: any): Promise<number | string>;
28
29
  getSeasonFederation(assortmentId: any): Promise<SeasonFederation>;
29
30
  getAssortment(assortmentId: any): Promise<any>;
30
31
  getSinceDate(assortmentId: any, assortmentPublishChangeId: any, apcHistory: object[]): Promise<Date>;
@@ -72,16 +72,52 @@ class BaseProcessPublishAssortment {
72
72
  }
73
73
  async getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher) {
74
74
  const apc = apcHistory?.find(apc => apc?.id === assortmentPublishChangeId) || {};
75
- const assortmentName = (await this.getAssortment(assortmentId))?.name;
75
+ const assortment = await this.getAssortment(assortmentId);
76
+ const assortmentName = assortment?.name;
77
+ const versionHistoryNumber = await this.getSnapshotVersion(assortment, apc);
76
78
  const publishInfo = {
77
79
  assortmentName,
78
80
  versionName: apc?.versionName,
79
81
  versionComments: apc?.versionComments,
80
82
  publishDate: apc?.createdOn,
81
- publisher
83
+ publisher,
84
+ versionHistoryNumber
82
85
  };
83
86
  return publishInfo;
84
87
  }
88
+ async getSnapshotVersion(assortment, apc) {
89
+ const entityReference = assortment?.createdForReference;
90
+ const createdOnString = apc?.createdOn;
91
+ if (!entityReference || !createdOnString || !apc?.id) {
92
+ return 'unknown';
93
+ }
94
+ const createdOnDate = new Date(createdOnString);
95
+ createdOnDate.setDate(createdOnDate.getDate() - 1);
96
+ const createdOnStringMinus1 = createdOnDate.toISOString();
97
+ const createdOn = 'ISGREATERTHAN ' + createdOnStringMinus1;
98
+ const snapshots = await new sdk_1.Entities().get({
99
+ entityName: 'entity-snapshot',
100
+ criteria: {
101
+ entityReference,
102
+ createdOn
103
+ }
104
+ });
105
+ const assortmentPublishChangeId = apc?.id;
106
+ const snapshot = snapshots.find(snap => snap?.assortmentChangeId === assortmentPublishChangeId);
107
+ let snapshotVersion = 'unknown';
108
+ if (snapshot && snapshot.versionNumber) {
109
+ snapshotVersion = snapshot.versionNumber;
110
+ }
111
+ else if (!snapshot) {
112
+ const createdOnTime = new Date(createdOnString).getTime();
113
+ const lastSnapshot = snapshots[snapshots.length - 1];
114
+ const lastSnapshotTime = new Date(lastSnapshot.createdOn).getTime();
115
+ if (createdOnTime > lastSnapshotTime) {
116
+ snapshotVersion = 'after: ' + lastSnapshot.versionNumber;
117
+ }
118
+ }
119
+ return snapshotVersion;
120
+ }
85
121
  async getSeasonFederation(assortmentId) {
86
122
  const assortment = await this.getAssortment(assortmentId);
87
123
  console.error('assortment-name: ' + assortment?.name);
@@ -28,11 +28,13 @@ jest.mock('../util/federation', () => {
28
28
  }
29
29
  };
30
30
  });
31
+ let entityObject = {};
32
+ let getOptionsObject = {};
31
33
  jest.mock('@contrail/sdk', () => {
32
- const entityObject = { publishToFlexPLM: true };
33
34
  return {
34
35
  Entities: class {
35
- get() {
36
+ get(_getOtionsObject) {
37
+ getOptionsObject = _getOtionsObject;
36
38
  return entityObject;
37
39
  }
38
40
  }
@@ -46,6 +48,8 @@ describe('process publish assortment', () => {
46
48
  };
47
49
  beforeEach(() => {
48
50
  federatedId = '';
51
+ entityObject = { publishToFlexPLM: true };
52
+ getOptionsObject = {};
49
53
  });
50
54
  it('No Federation Info', async () => {
51
55
  const event = {
@@ -80,17 +84,24 @@ describe('getPublishInfo', () => {
80
84
  email: 'chris@vibeiq.com'
81
85
  };
82
86
  const assortmentName = 'VibeIQ Fall 2023';
83
- const spy = jest.spyOn(ppa, 'getAssortment')
87
+ const versionHistoryNumber = 1234;
88
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
84
89
  .mockImplementation(async (assortmentId) => {
85
90
  return { id: assortmentId, name: assortmentName };
86
91
  });
92
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
93
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
94
+ return versionHistoryNumber;
95
+ });
87
96
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
88
97
  expect(publishInfo.assortmentName).toEqual(assortmentName);
89
98
  expect(publishInfo.versionName).toEqual(versionName);
90
99
  expect(publishInfo.versionComments).toEqual(versionComments);
91
100
  expect(publishInfo.publishDate).toEqual(createdOn);
92
101
  expect(publishInfo.publisher).toEqual(publisher);
93
- spy.mockRestore();
102
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
103
+ spyGetAssortment.mockRestore();
104
+ spyyGetSnapshotVersion.mockRestore();
94
105
  });
95
106
  it('No Assortment Name', async () => {
96
107
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
@@ -98,17 +109,24 @@ describe('getPublishInfo', () => {
98
109
  const publisher = {
99
110
  email: 'chris@vibeiq.com'
100
111
  };
101
- const spy = jest.spyOn(ppa, 'getAssortment')
112
+ const versionHistoryNumber = 1234;
113
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
102
114
  .mockImplementation(async (assortmentId) => {
103
115
  return { id: assortmentId, name: undefined };
104
116
  });
117
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
118
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
119
+ return versionHistoryNumber;
120
+ });
105
121
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
106
122
  expect(publishInfo.assortmentName).toBeUndefined;
107
123
  expect(publishInfo.versionName).toEqual(versionName);
108
124
  expect(publishInfo.versionComments).toEqual(versionComments);
109
125
  expect(publishInfo.publishDate).toEqual(createdOn);
110
126
  expect(publishInfo.publisher).toEqual(publisher);
111
- spy.mockRestore();
127
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
128
+ spyGetAssortment.mockRestore();
129
+ spyyGetSnapshotVersion.mockRestore();
112
130
  });
113
131
  it('No Assortment', async () => {
114
132
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
@@ -116,17 +134,24 @@ describe('getPublishInfo', () => {
116
134
  const publisher = {
117
135
  email: 'chris@vibeiq.com'
118
136
  };
119
- const spy = jest.spyOn(ppa, 'getAssortment')
137
+ const versionHistoryNumber = 1234;
138
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
120
139
  .mockImplementation(async (assortmentId) => {
121
140
  return undefined;
122
141
  });
142
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
143
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
144
+ return versionHistoryNumber;
145
+ });
123
146
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
124
147
  expect(publishInfo.assortmentName).toBeUndefined();
125
148
  expect(publishInfo.versionName).toEqual(versionName);
126
149
  expect(publishInfo.versionComments).toEqual(versionComments);
127
150
  expect(publishInfo.publishDate).toEqual(createdOn);
128
151
  expect(publishInfo.publisher).toEqual(publisher);
129
- spy.mockRestore();
152
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
153
+ spyGetAssortment.mockRestore();
154
+ spyyGetSnapshotVersion.mockRestore();
130
155
  });
131
156
  it('Empty Version Name & Comments', async () => {
132
157
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
@@ -143,34 +168,48 @@ describe('getPublishInfo', () => {
143
168
  email: 'chris@vibeiq.com'
144
169
  };
145
170
  const assortmentName = 'VibeIQ Fall 2023';
146
- const spy = jest.spyOn(ppa, 'getAssortment')
171
+ const versionHistoryNumber = 1234;
172
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
147
173
  .mockImplementation(async (assortmentId) => {
148
174
  return { id: assortmentId, name: assortmentName };
149
175
  });
176
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
177
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
178
+ return versionHistoryNumber;
179
+ });
150
180
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, history, publisher);
151
181
  expect(publishInfo.assortmentName).toEqual(assortmentName);
152
182
  expect(publishInfo.versionName).toEqual('');
153
183
  expect(publishInfo.versionComments).toEqual('');
154
184
  expect(publishInfo.publishDate).toEqual(createdOn);
155
185
  expect(publishInfo.publisher).toEqual(publisher);
156
- spy.mockRestore();
186
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
187
+ spyGetAssortment.mockRestore();
188
+ spyyGetSnapshotVersion.mockRestore();
157
189
  });
158
190
  it('Undefined Publisher', async () => {
159
191
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
160
192
  const assortmentPublishChangeId = 'sJbGx1Fpq1v2MmcI';
161
193
  const publisher = undefined;
162
194
  const assortmentName = 'VibeIQ Fall 2023';
163
- const spy = jest.spyOn(ppa, 'getAssortment')
195
+ const versionHistoryNumber = 1234;
196
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
164
197
  .mockImplementation(async (assortmentId) => {
165
198
  return { id: assortmentId, name: assortmentName };
166
199
  });
200
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
201
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
202
+ return versionHistoryNumber;
203
+ });
167
204
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
168
205
  expect(publishInfo.assortmentName).toEqual(assortmentName);
169
206
  expect(publishInfo.versionName).toEqual(versionName);
170
207
  expect(publishInfo.versionComments).toEqual(versionComments);
171
208
  expect(publishInfo.publishDate).toEqual(createdOn);
172
209
  expect(publishInfo.publisher).toBeUndefined();
173
- spy.mockRestore();
210
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
211
+ spyGetAssortment.mockRestore();
212
+ spyyGetSnapshotVersion.mockRestore();
174
213
  });
175
214
  it('No APC', async () => {
176
215
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
@@ -179,17 +218,24 @@ describe('getPublishInfo', () => {
179
218
  email: 'chris@vibeiq.com'
180
219
  };
181
220
  const assortmentName = 'VibeIQ Fall 2023';
182
- const spy = jest.spyOn(ppa, 'getAssortment')
221
+ const versionHistoryNumber = 1234;
222
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
183
223
  .mockImplementation(async (assortmentId) => {
184
224
  return { id: assortmentId, name: assortmentName };
185
225
  });
226
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
227
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
228
+ return versionHistoryNumber;
229
+ });
186
230
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
187
231
  expect(publishInfo.assortmentName).toEqual(assortmentName);
188
232
  expect(publishInfo.versionName).toBeUndefined();
189
233
  expect(publishInfo.versionComments).toBeUndefined();
190
234
  expect(publishInfo.publishDate).toBeUndefined();
191
235
  expect(publishInfo.publisher).toEqual(publisher);
192
- spy.mockRestore();
236
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
237
+ spyGetAssortment.mockRestore();
238
+ spyyGetSnapshotVersion.mockRestore();
193
239
  });
194
240
  it('No APC history', async () => {
195
241
  const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
@@ -198,17 +244,125 @@ describe('getPublishInfo', () => {
198
244
  email: 'chris@vibeiq.com'
199
245
  };
200
246
  const assortmentName = 'VibeIQ Fall 2023';
201
- const spy = jest.spyOn(ppa, 'getAssortment')
247
+ const versionHistoryNumber = 1234;
248
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
202
249
  .mockImplementation(async (assortmentId) => {
203
250
  return { id: assortmentId, name: assortmentName };
204
251
  });
252
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
253
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
254
+ return versionHistoryNumber;
255
+ });
205
256
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, undefined, publisher);
206
257
  expect(publishInfo.assortmentName).toEqual(assortmentName);
207
258
  expect(publishInfo.versionName).toBeUndefined();
208
259
  expect(publishInfo.versionComments).toBeUndefined();
209
260
  expect(publishInfo.publishDate).toBeUndefined();
210
261
  expect(publishInfo.publisher).toEqual(publisher);
211
- spy.mockRestore();
262
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
263
+ spyGetAssortment.mockRestore();
264
+ spyyGetSnapshotVersion.mockRestore();
265
+ });
266
+ });
267
+ describe('getSnapshotVersion', () => {
268
+ const config = {
269
+ identifierAtts: {
270
+ LCSSeason: ['flexPLMSeasonName']
271
+ }
272
+ };
273
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
274
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
275
+ const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
276
+ beforeEach(() => {
277
+ federatedId = '';
278
+ entityObject = { publishToFlexPLM: true };
279
+ getOptionsObject = {};
280
+ });
281
+ it('has snapshot version', async () => {
282
+ const assortment = {
283
+ id: 'rm35clTNxvS4wm6y',
284
+ name: 'VibeIQ Fall 2023',
285
+ createdForReference: 'plan:w23wre'
286
+ };
287
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
288
+ const apc = {
289
+ id: assortmentChangeId,
290
+ versionName: 'Version 1',
291
+ versionComments: 'Some Comments',
292
+ createdOn: '2023-01-15T19:13:58.902Z'
293
+ };
294
+ const versionNumber = 1234;
295
+ const snapshots = [
296
+ {
297
+ id: 'qewrwer',
298
+ assortmentChangeId,
299
+ versionNumber
300
+ }
301
+ ];
302
+ entityObject = snapshots;
303
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
304
+ expect(versionHistoryNumber).toEqual(versionNumber);
305
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
306
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
307
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
308
+ });
309
+ it('doesnt find snapshot version, last snapshot after apc', async () => {
310
+ const assortment = {
311
+ id: 'rm35clTNxvS4wm6y',
312
+ name: 'VibeIQ Fall 2023',
313
+ createdForReference: 'plan:w23wre'
314
+ };
315
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
316
+ const apc = {
317
+ id: assortmentChangeId,
318
+ versionName: 'Version 1',
319
+ versionComments: 'Some Comments',
320
+ createdOn: '2023-01-15T19:13:58.902Z'
321
+ };
322
+ const versionNumber = 1234;
323
+ const snapshots = [
324
+ {
325
+ id: 'qewrwer',
326
+ assortmentChangeId: 'bad-id',
327
+ createdOn: '2024-01-15T19:13:58.902Z',
328
+ versionNumber
329
+ }
330
+ ];
331
+ entityObject = snapshots;
332
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
333
+ expect(versionHistoryNumber).toEqual('unknown');
334
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
335
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
336
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
337
+ });
338
+ it('doesnt find snapshot version, last snapshot before apc', async () => {
339
+ const assortment = {
340
+ id: 'rm35clTNxvS4wm6y',
341
+ name: 'VibeIQ Fall 2023',
342
+ createdForReference: 'plan:w23wre'
343
+ };
344
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
345
+ const apc = {
346
+ id: assortmentChangeId,
347
+ versionName: 'Version 1',
348
+ versionComments: 'Some Comments',
349
+ createdOn: '2023-01-15T19:13:58.902Z'
350
+ };
351
+ const versionNumber = 1234;
352
+ const snapshots = [
353
+ {
354
+ id: 'qewrwer',
355
+ assortmentChangeId: 'bad-id',
356
+ createdOn: '2023-01-14T19:13:58.902Z',
357
+ versionNumber
358
+ }
359
+ ];
360
+ entityObject = snapshots;
361
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
362
+ expect(versionHistoryNumber).toEqual('after: ' + versionNumber);
363
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
364
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
365
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
212
366
  });
213
367
  });
214
368
  describe('getSeasonFederation', () => {
@@ -219,6 +373,8 @@ describe('getSeasonFederation', () => {
219
373
  };
220
374
  beforeEach(() => {
221
375
  federatedId = '';
376
+ entityObject = { publishToFlexPLM: true };
377
+ getOptionsObject = {};
222
378
  });
223
379
  it('No Federation Info', async () => {
224
380
  const assortmentId = 'B3oRQpYVYzcJAJtQ';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.1.40",
3
+ "version": "1.1.42",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -176,6 +176,8 @@ export abstract class BaseEntityProcessor {
176
176
  return await this.handleOutgoingUpsert(entityType, event);
177
177
  case 'delete':
178
178
  return await this.handleOutgoingDelete(entityType, event);
179
+ case 'sendUpsertToFlexPLM':
180
+ return await this.sendUpsertToFlexPLM(event);
179
181
  default:
180
182
  console.log(UNSUPPORTED_TYPE);
181
183
  return {
@@ -223,4 +225,82 @@ export abstract class BaseEntityProcessor {
223
225
 
224
226
  // This method must be implemented by derived classes
225
227
  protected abstract getOutgoingUpsertPayload(entityType, event): Promise<EntityPayloadType>;
228
+
229
+ /** Create a new event-workflow-request to rerun sending the entity to FlexPLM
230
+ * The event must contain any information needed to ensure it is put in the correct queue for the entity
231
+ *
232
+ * @param triggerKey Ex: event.entityType + '|sendUpsertToFlexPLM'
233
+ * @param event
234
+ * @returns
235
+ */
236
+
237
+ protected async triggerNewEvent(triggerKey: string, event: any) {
238
+ const newEvent = {
239
+ entityName: 'event-workflow-request',
240
+ object: {
241
+ triggerKey,
242
+ event
243
+ }
244
+ };
245
+ const response = await this.entities.create(newEvent);
246
+
247
+ return response;
248
+ }
249
+
250
+ /** Sends the current state of the entity to FlexPLM.
251
+ * So any changes made in Vibe between the event being generated and the event being processed are sent to FlexPLM.
252
+ *
253
+ * @param event must contain entityType, id; which are used to query for the entity
254
+ * @returns results of sending the entity to FlexPLM
255
+ */
256
+ protected async sendUpsertToFlexPLM(event) {
257
+ const payload = await this.getEntityCurrentStateUpsertPayload(event);
258
+ if(!payload){
259
+ const message = 'No payload to send to FlexPLM';
260
+ console.log(message);
261
+ return {
262
+ status: 500,
263
+ data: {message}
264
+ };
265
+ };
266
+ const flexResponse: any = await new FlexPLMConnect(this.config).sendToFlexPLM(payload);
267
+
268
+ const outboundEntityUpdates = await this.getOutboundEntityUpdates(event, flexResponse);
269
+ if(outboundEntityUpdates){
270
+ flexResponse['outboundEntityUpdates'] = outboundEntityUpdates;
271
+ }
272
+
273
+ return flexResponse;
274
+
275
+ }
276
+
277
+ /** Generates the payload to send to FlexPLM, based on the current state of the entity.
278
+ * The current state of the entity are used as the newData and oldData; which is passed
279
+ * to getOutgoingUpsertPayload to generate the payload.
280
+ * @param event information about the item to send to FlexPLM
281
+ * @returns The payload to send to FlexPLM
282
+ */
283
+ protected async getEntityCurrentStateUpsertPayload(event: any): Promise<EntityPayloadType> {
284
+
285
+ const id = event.id;
286
+ if(!id){
287
+ return undefined;
288
+ }
289
+
290
+ const entity = await this.entities.get({
291
+ entityName: this.baseType,
292
+ id
293
+ });
294
+
295
+ if(!entity){
296
+ return undefined;
297
+ }
298
+
299
+ event.newData = entity;
300
+ event.oldData = entity;
301
+
302
+ const payload = await this.getOutgoingUpsertPayload(this.baseType, event);
303
+
304
+ return payload;
305
+ }
226
306
  }
@@ -30,11 +30,13 @@ jest.mock('../util/federation', () => {
30
30
  }
31
31
  };
32
32
  });
33
+ let entityObject = {};
34
+ let getOptionsObject = {};
33
35
  jest.mock('@contrail/sdk', () => {
34
- const entityObject = { publishToFlexPLM: true };
35
36
  return {
36
- Entities: class {
37
- get() {
37
+ Entities: class{
38
+ get(_getOtionsObject){
39
+ getOptionsObject = _getOtionsObject;
38
40
  return entityObject;
39
41
  }
40
42
  }
@@ -48,6 +50,8 @@ describe('process publish assortment', () => {
48
50
  } as unknown as FCConfig;
49
51
  beforeEach(() => {
50
52
  federatedId = '';
53
+ entityObject = {publishToFlexPLM:true};
54
+ getOptionsObject = {};
51
55
  });
52
56
  it('No Federation Info', async () => {
53
57
  const event = {
@@ -86,17 +90,25 @@ describe('getPublishInfo', () =>{
86
90
  email: 'chris@vibeiq.com'
87
91
  };
88
92
  const assortmentName = 'VibeIQ Fall 2023';
89
- const spy = jest.spyOn(ppa, 'getAssortment')
93
+ const versionHistoryNumber = 1234;
94
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
90
95
  .mockImplementation(async (assortmentId) => {
91
96
  return {id: assortmentId, name: assortmentName};
92
97
  });
98
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
99
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
100
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
101
+ return versionHistoryNumber;
102
+ });
93
103
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
94
104
  expect(publishInfo.assortmentName).toEqual(assortmentName);
95
105
  expect(publishInfo.versionName).toEqual(versionName);
96
106
  expect(publishInfo.versionComments).toEqual(versionComments);
97
107
  expect(publishInfo.publishDate).toEqual(createdOn);
98
108
  expect(publishInfo.publisher).toEqual(publisher);
99
- spy.mockRestore();
109
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
110
+ spyGetAssortment.mockRestore();
111
+ spyyGetSnapshotVersion.mockRestore();
100
112
  });
101
113
 
102
114
  it('No Assortment Name', async () =>{
@@ -106,17 +118,25 @@ describe('getPublishInfo', () =>{
106
118
  const publisher = {
107
119
  email: 'chris@vibeiq.com'
108
120
  };
109
- const spy = jest.spyOn(ppa, 'getAssortment')
121
+ const versionHistoryNumber = 1234;
122
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
110
123
  .mockImplementation(async (assortmentId) => {
111
124
  return {id: assortmentId, name: undefined};
112
125
  });
126
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
127
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
128
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
129
+ return versionHistoryNumber;
130
+ });
113
131
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
114
132
  expect(publishInfo.assortmentName).toBeUndefined;
115
133
  expect(publishInfo.versionName).toEqual(versionName);
116
134
  expect(publishInfo.versionComments).toEqual(versionComments);
117
135
  expect(publishInfo.publishDate).toEqual(createdOn);
118
136
  expect(publishInfo.publisher).toEqual(publisher);
119
- spy.mockRestore();
137
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
138
+ spyGetAssortment.mockRestore();
139
+ spyyGetSnapshotVersion.mockRestore();
120
140
  });
121
141
 
122
142
  it('No Assortment', async () =>{
@@ -126,17 +146,25 @@ describe('getPublishInfo', () =>{
126
146
  const publisher = {
127
147
  email: 'chris@vibeiq.com'
128
148
  };
129
- const spy = jest.spyOn(ppa, 'getAssortment')
149
+ const versionHistoryNumber = 1234;
150
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
130
151
  .mockImplementation(async (assortmentId) => {
131
152
  return undefined;
132
153
  });
154
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
155
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
156
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
157
+ return versionHistoryNumber;
158
+ });
133
159
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
134
160
  expect(publishInfo.assortmentName).toBeUndefined();
135
161
  expect(publishInfo.versionName).toEqual(versionName);
136
162
  expect(publishInfo.versionComments).toEqual(versionComments);
137
163
  expect(publishInfo.publishDate).toEqual(createdOn);
138
164
  expect(publishInfo.publisher).toEqual(publisher);
139
- spy.mockRestore();
165
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
166
+ spyGetAssortment.mockRestore();
167
+ spyyGetSnapshotVersion.mockRestore();
140
168
  });
141
169
 
142
170
  it('Empty Version Name & Comments', async () =>{
@@ -154,17 +182,25 @@ describe('getPublishInfo', () =>{
154
182
  email: 'chris@vibeiq.com'
155
183
  };
156
184
  const assortmentName = 'VibeIQ Fall 2023';
157
- const spy = jest.spyOn(ppa, 'getAssortment')
185
+ const versionHistoryNumber = 1234;
186
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
158
187
  .mockImplementation(async (assortmentId) => {
159
188
  return {id: assortmentId, name: assortmentName};
160
189
  });
190
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
191
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
192
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
193
+ return versionHistoryNumber;
194
+ });
161
195
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, history, publisher);
162
196
  expect(publishInfo.assortmentName).toEqual(assortmentName);
163
197
  expect(publishInfo.versionName).toEqual('');
164
198
  expect(publishInfo.versionComments).toEqual('');
165
199
  expect(publishInfo.publishDate).toEqual(createdOn);
166
200
  expect(publishInfo.publisher).toEqual(publisher);
167
- spy.mockRestore();
201
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
202
+ spyGetAssortment.mockRestore();
203
+ spyyGetSnapshotVersion.mockRestore();
168
204
  });
169
205
 
170
206
  it('Undefined Publisher', async () =>{
@@ -173,17 +209,25 @@ describe('getPublishInfo', () =>{
173
209
  const assortmentPublishChangeId = 'sJbGx1Fpq1v2MmcI';
174
210
  const publisher = undefined;
175
211
  const assortmentName = 'VibeIQ Fall 2023';
176
- const spy = jest.spyOn(ppa, 'getAssortment')
212
+ const versionHistoryNumber = 1234;
213
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
177
214
  .mockImplementation(async (assortmentId) => {
178
215
  return {id: assortmentId, name: assortmentName};
179
216
  });
217
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
218
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
219
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
220
+ return versionHistoryNumber;
221
+ });
180
222
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
181
223
  expect(publishInfo.assortmentName).toEqual(assortmentName);
182
224
  expect(publishInfo.versionName).toEqual(versionName);
183
225
  expect(publishInfo.versionComments).toEqual(versionComments);
184
226
  expect(publishInfo.publishDate).toEqual(createdOn);
185
227
  expect(publishInfo.publisher).toBeUndefined();
186
- spy.mockRestore();
228
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
229
+ spyGetAssortment.mockRestore();
230
+ spyyGetSnapshotVersion.mockRestore();
187
231
  });
188
232
 
189
233
  it('No APC', async () =>{
@@ -194,17 +238,25 @@ describe('getPublishInfo', () =>{
194
238
  email: 'chris@vibeiq.com'
195
239
  };
196
240
  const assortmentName = 'VibeIQ Fall 2023';
197
- const spy = jest.spyOn(ppa, 'getAssortment')
241
+ const versionHistoryNumber = 1234;
242
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
198
243
  .mockImplementation(async (assortmentId) => {
199
244
  return {id: assortmentId, name: assortmentName};
200
245
  });
246
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
247
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
248
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
249
+ return versionHistoryNumber;
250
+ });
201
251
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, plan_history, publisher);
202
252
  expect(publishInfo.assortmentName).toEqual(assortmentName);
203
253
  expect(publishInfo.versionName).toBeUndefined();
204
254
  expect(publishInfo.versionComments).toBeUndefined();
205
255
  expect(publishInfo.publishDate).toBeUndefined();
206
256
  expect(publishInfo.publisher).toEqual(publisher);
207
- spy.mockRestore();
257
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
258
+ spyGetAssortment.mockRestore();
259
+ spyyGetSnapshotVersion.mockRestore();
208
260
  });
209
261
 
210
262
  it('No APC history', async () =>{
@@ -215,21 +267,148 @@ describe('getPublishInfo', () =>{
215
267
  email: 'chris@vibeiq.com'
216
268
  };
217
269
  const assortmentName = 'VibeIQ Fall 2023';
218
- const spy = jest.spyOn(ppa, 'getAssortment')
270
+ const versionHistoryNumber = 1234;
271
+ const spyGetAssortment = jest.spyOn(ppa, 'getAssortment')
219
272
  .mockImplementation(async (assortmentId) => {
220
273
  return {id: assortmentId, name: assortmentName};
221
274
  });
275
+ const spyyGetSnapshotVersion = jest.spyOn(ppa, 'getSnapshotVersion')
276
+ // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
277
+ .mockImplementation(async (assortmentId, assortmentPublishChangeId) => {
278
+ return versionHistoryNumber;
279
+ });
222
280
  const publishInfo = await ppa.getPublishInfo(assortmentId, assortmentPublishChangeId, undefined, publisher);
223
281
  expect(publishInfo.assortmentName).toEqual(assortmentName);
224
282
  expect(publishInfo.versionName).toBeUndefined();
225
283
  expect(publishInfo.versionComments).toBeUndefined();
226
284
  expect(publishInfo.publishDate).toBeUndefined();
227
285
  expect(publishInfo.publisher).toEqual(publisher);
228
- spy.mockRestore();
286
+ expect(publishInfo.versionHistoryNumber).toEqual(versionHistoryNumber);
287
+ spyGetAssortment.mockRestore();
288
+ spyyGetSnapshotVersion.mockRestore();
229
289
  });
230
290
 
231
291
  });
232
- describe('getSeasonFederation', () => {
292
+
293
+ describe('getSnapshotVersion', () =>{
294
+ const config = {
295
+ identifierAtts: {
296
+ LCSSeason: ['flexPLMSeasonName']
297
+ }
298
+ } as unknown as FCConfig;
299
+ const mapFileUtil = new MapFileUtil(new Entities());
300
+ const dc = new DataConverter(config, mapFileUtil);
301
+ const ppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
302
+ beforeEach(() =>{
303
+ federatedId = '';
304
+ entityObject = { publishToFlexPLM:true};
305
+ getOptionsObject = {};
306
+ });
307
+
308
+ it('has snapshot version', async () =>{
309
+ const assortment = {
310
+ id: 'rm35clTNxvS4wm6y',
311
+ name: 'VibeIQ Fall 2023',
312
+ createdForReference: 'plan:w23wre'
313
+ };
314
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
315
+ const apc = {
316
+ id: assortmentChangeId,
317
+ versionName: 'Version 1',
318
+ versionComments: 'Some Comments',
319
+ createdOn: '2023-01-15T19:13:58.902Z'
320
+ };
321
+
322
+ const versionNumber = 1234;
323
+ const snapshots = [
324
+ {
325
+ id: 'qewrwer',
326
+ assortmentChangeId,
327
+ versionNumber
328
+ }
329
+ ];
330
+
331
+ entityObject = snapshots;
332
+
333
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
334
+
335
+ expect(versionHistoryNumber).toEqual(versionNumber);
336
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
337
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
338
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
339
+ });
340
+
341
+ it('doesnt find snapshot version, last snapshot after apc', async () =>{
342
+ const assortment = {
343
+ id: 'rm35clTNxvS4wm6y',
344
+ name: 'VibeIQ Fall 2023',
345
+ createdForReference: 'plan:w23wre'
346
+ };
347
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
348
+ const apc = {
349
+ id: assortmentChangeId,
350
+ versionName: 'Version 1',
351
+ versionComments: 'Some Comments',
352
+ createdOn: '2023-01-15T19:13:58.902Z'
353
+ };
354
+
355
+ const versionNumber = 1234;
356
+ const snapshots = [
357
+ {
358
+ id: 'qewrwer',
359
+ assortmentChangeId: 'bad-id',
360
+ createdOn: '2024-01-15T19:13:58.902Z',
361
+ versionNumber
362
+ }
363
+ ];
364
+
365
+ entityObject = snapshots;
366
+
367
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
368
+
369
+ expect(versionHistoryNumber).toEqual('unknown');
370
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
371
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
372
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
373
+ });
374
+
375
+ it('doesnt find snapshot version, last snapshot before apc', async () =>{
376
+ const assortment = {
377
+ id: 'rm35clTNxvS4wm6y',
378
+ name: 'VibeIQ Fall 2023',
379
+ createdForReference: 'plan:w23wre'
380
+ };
381
+ const assortmentChangeId = 'sJbGx1Fpq1v2MmcI';
382
+ const apc = {
383
+ id: assortmentChangeId,
384
+ versionName: 'Version 1',
385
+ versionComments: 'Some Comments',
386
+ createdOn: '2023-01-15T19:13:58.902Z'
387
+ };
388
+
389
+ const versionNumber = 1234;
390
+ const snapshots = [
391
+ {
392
+ id: 'qewrwer',
393
+ assortmentChangeId: 'bad-id',
394
+ createdOn: '2023-01-14T19:13:58.902Z',
395
+ versionNumber
396
+ }
397
+ ];
398
+
399
+ entityObject = snapshots;
400
+
401
+ const versionHistoryNumber = await ppa.getSnapshotVersion(assortment, apc);
402
+
403
+ expect(versionHistoryNumber).toEqual('after: '+ versionNumber);
404
+ expect(getOptionsObject['entityName']).toEqual('entity-snapshot');
405
+ expect(getOptionsObject['criteria']['entityReference']).toEqual(assortment.createdForReference);
406
+ expect(getOptionsObject['criteria']['createdOn'].indexOf('ISGREATERTHAN')).toEqual(0);
407
+ });
408
+
409
+ });
410
+
411
+ describe('getSeasonFederation', () =>{
233
412
  const config = {
234
413
  identifierAtts: {
235
414
  LCSSeason: ['flexPLMSeasonName']
@@ -237,6 +416,8 @@ describe('getSeasonFederation', () => {
237
416
  } as unknown as FCConfig;
238
417
  beforeEach(() => {
239
418
  federatedId = '';
419
+ entityObject = { publishToFlexPLM:true};
420
+ getOptionsObject = {};
240
421
  });
241
422
  it('No Federation Info', async () => {
242
423
  const assortmentId = 'B3oRQpYVYzcJAJtQ';
@@ -95,18 +95,66 @@ export class BaseProcessPublishAssortment {
95
95
  }
96
96
  async getPublishInfo(assortmentId: string, assortmentPublishChangeId: string, apcHistory: any, publisher: any): Promise<any> {
97
97
  const apc = apcHistory?.find(apc => apc?.id === assortmentPublishChangeId) || {};
98
- const assortmentName = (await this.getAssortment(assortmentId))?.name;
98
+ const assortment = await this.getAssortment(assortmentId);
99
+ const assortmentName = assortment?.name;
100
+ const versionHistoryNumber = await this.getSnapshotVersion(assortment, apc);
99
101
  const publishInfo = {
100
102
  assortmentName,
101
103
  versionName: apc?.versionName,
102
104
  versionComments: apc?.versionComments,
103
105
  publishDate: apc?.createdOn,
104
- publisher
106
+ publisher,
107
+ versionHistoryNumber
105
108
  };
106
109
 
107
110
  return publishInfo;
108
111
  }
109
112
 
113
+ /** Gets the version number of the snapshot that was created for the publish change.
114
+ * But if no snapshot was found for the publish change, and the last snapshot was
115
+ * created before the publish change, then it returns 'after: versionNumber' where
116
+ * versionNumber is the version number of the last snapshot.
117
+ * If no snapshot was found for the publish change, and the last snapshot was
118
+ * created after the publish change, then it returns 'unknown'.
119
+ *
120
+ * @returns versionNumber or 'unknown' or 'after: versionNumber'
121
+ */
122
+ async getSnapshotVersion(assortment: any, apc: any ): Promise<number|string>{
123
+ const entityReference = assortment?.createdForReference;
124
+ const createdOnString = apc?.createdOn;
125
+ if(!entityReference || !createdOnString || !apc?.id){
126
+ return 'unknown';
127
+ }
128
+ const createdOnDate = new Date(createdOnString);
129
+ createdOnDate.setDate(createdOnDate.getDate() - 1);//subtract 1 day
130
+ const createdOnStringMinus1 = createdOnDate.toISOString();
131
+ const createdOn = 'ISGREATERTHAN ' + createdOnStringMinus1;
132
+
133
+ const snapshots = await new Entities().get({
134
+ entityName: 'entity-snapshot',
135
+ criteria: {
136
+ entityReference,
137
+ createdOn
138
+ }
139
+ });
140
+
141
+ const assortmentPublishChangeId = apc?.id;
142
+ const snapshot = snapshots.find(snap => snap?.assortmentChangeId === assortmentPublishChangeId);
143
+ let snapshotVersion = 'unknown';
144
+ if(snapshot && snapshot.versionNumber){
145
+ snapshotVersion = snapshot.versionNumber;
146
+ } else if(!snapshot){
147
+ const createdOnTime = new Date(createdOnString).getTime();
148
+ const lastSnapshot = snapshots[snapshots.length - 1];
149
+ const lastSnapshotTime = new Date(lastSnapshot.createdOn).getTime();
150
+ if(createdOnTime > lastSnapshotTime){
151
+ snapshotVersion = 'after: ' + lastSnapshot.versionNumber;
152
+ }
153
+
154
+ }
155
+
156
+ return snapshotVersion;
157
+ }
110
158
  async getSeasonFederation(assortmentId): Promise<SeasonFederation> {
111
159
  const assortment = await this.getAssortment(assortmentId);
112
160
  console.error('assortment-name: ' + assortment?.name);