mcdev 7.5.0 → 7.6.1

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 (53) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/@types/lib/index.d.ts +12 -8
  3. package/@types/lib/index.d.ts.map +1 -1
  4. package/@types/lib/metadataTypes/AttributeSet.d.ts.map +1 -1
  5. package/@types/lib/metadataTypes/Automation.d.ts +14 -0
  6. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/DataExtension.d.ts +3 -1
  8. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Event.d.ts +2 -2
  10. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Journey.d.ts +17 -0
  12. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/MetadataType.d.ts +8 -4
  14. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/Query.d.ts +7 -0
  16. package/@types/lib/metadataTypes/Query.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/TransactionalEmail.d.ts +1 -0
  18. package/@types/lib/metadataTypes/TransactionalEmail.d.ts.map +1 -1
  19. package/@types/lib/metadataTypes/TriggeredSend.d.ts +0 -8
  20. package/@types/lib/metadataTypes/TriggeredSend.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +1 -0
  22. package/@types/lib/metadataTypes/definitions/TransactionalEmail.definition.d.ts +1 -0
  23. package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
  24. package/@types/lib/util/util.d.ts.map +1 -1
  25. package/@types/types/mcdev.d.d.ts +20 -8
  26. package/@types/types/mcdev.d.d.ts.map +1 -1
  27. package/lib/cli.js +13 -2
  28. package/lib/index.js +46 -45
  29. package/lib/metadataTypes/Automation.js +28 -0
  30. package/lib/metadataTypes/Event.js +4 -1
  31. package/lib/metadataTypes/Journey.js +472 -94
  32. package/lib/metadataTypes/MetadataType.js +12 -6
  33. package/lib/metadataTypes/TriggeredSend.js +46 -15
  34. package/lib/metadataTypes/definitions/Automation.definition.js +1 -0
  35. package/lib/metadataTypes/definitions/Journey.definition.js +1 -0
  36. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +1 -0
  37. package/package.json +15 -15
  38. package/test/general.test.js +5 -5
  39. package/test/mockRoot/.mcdevrc.json +1 -1
  40. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINcontextual_suppression_list,hidden,list,mysubs,publication,suppression_list,triggered_send,triggered_send_journeybuilder-response.xml +385 -0
  41. package/test/resources/9999999/interaction/v1/interactions/0175b971-71a3-4d8e-98ac-48121f3fbf4f/get-response.json +461 -0
  42. package/test/resources/9999999/interaction/v1/interactions/dsfdsafdsa-922c-4568-85a5-e5cc77efc3be/delete-response.txt +1 -0
  43. package/test/resources/9999999/interaction/v1/interactions/transactional/pause/post-response.json +3 -0
  44. package/test/resources/9999999/interaction/v1/interactions/transactional/resume/post-response.json +3 -0
  45. package/test/resources/9999999/triggeredSendDefinition/retrieve-CustomerKey=testExisting_triggeredSend-response.xml +72 -0
  46. package/test/resources/9999999/triggeredSendDefinition/retrieve-CustomerKey=testExisting_triggeredSend_rcb-response.xml +72 -0
  47. package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml +1 -1
  48. package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINdummy,Active-response.xml +118 -0
  49. package/test/type.automation.test.js +13 -13
  50. package/test/type.journey.test.js +79 -5
  51. package/test/type.query.test.js +2 -2
  52. package/test/type.triggeredSend.test.js +68 -1
  53. package/types/mcdev.d.js +2 -1
@@ -2,6 +2,7 @@
2
2
 
3
3
  import MetadataType from './MetadataType.js';
4
4
  import TransactionalEmail from './TransactionalEmail.js';
5
+ import TriggeredSend from './TriggeredSend.js';
5
6
  import Event from './Event.js';
6
7
  import { Util } from '../util/util.js';
7
8
  import cache from '../util/cache.js';
@@ -187,7 +188,8 @@ class Journey extends MetadataType {
187
188
  [
188
189
  'Interaction matching key not found.',
189
190
  'Must provide a valid ID or Key parameter',
190
- ].includes(ex.message)
191
+ ].includes(ex.message) ||
192
+ (key && ex.code === 'ERR_BAD_REQUEST')
191
193
  ) {
192
194
  Util.logger.info(
193
195
  `Downloaded: ${this.definition.type} (0)${Util.getKeysString(
@@ -209,22 +211,32 @@ class Journey extends MetadataType {
209
211
  */
210
212
  static async deleteByKey(key) {
211
213
  let version;
212
- let singleKey = '';
214
+ let id;
215
+ let cachedJourney;
213
216
 
214
217
  if (key.startsWith('id:') || key.startsWith('%23')) {
215
218
  // ! allow selecting journeys by ID because that's what users see in the URL
216
219
  // if the key started with %23 assume an ID was copied from the URL but the user forgot to prefix it with id:
217
220
 
218
221
  // remove id: or %23
219
- singleKey = key.slice(3);
220
- if (singleKey.startsWith('%23')) {
222
+ id = key.slice(3);
223
+ if (id.startsWith('%23')) {
221
224
  // in the journey URL the Id is prefixed with an HTML-encoded "#" which could accidentally be copied by users
222
225
  // despite the slicing above, this still needs testing here because users might have prefixed the ID with id: but did not know to remove the #23
223
- singleKey = singleKey.slice(3);
226
+ id = id.slice(3);
224
227
  }
225
- if (singleKey.includes('/')) {
228
+ if (id.includes('/')) {
226
229
  // in the journey URL the version is appended after the ID, separated by a forward-slash.
227
- [singleKey, version] = singleKey.split('/');
230
+ [id, version] = id.split('/');
231
+ }
232
+ try {
233
+ const response = await this.client.rest.get(
234
+ `/interaction/v1/interactions/${encodeURIComponent(id)}?extras=activities`
235
+ );
236
+ const results = this.parseResponseBody(response, key);
237
+ cachedJourney = results[key];
238
+ } catch {
239
+ // handle below
228
240
  }
229
241
  } else {
230
242
  if (key.includes('/')) {
@@ -233,40 +245,75 @@ class Journey extends MetadataType {
233
245
  }
234
246
 
235
247
  // delete by key with specified version does not work, therefore we need to get the ID first
236
- const response = await this.client.rest.get(
237
- `/interaction/v1/interactions/key:${encodeURIComponent(key)}?extras=`
238
- );
239
- const results = this.parseResponseBody(response, key);
240
- singleKey = results[key].id;
241
- if (version && version !== '*' && version > results[key].version) {
242
- Util.logger.error(
243
- `The chosen version (${version}) is higher than the latest known version (${results[key].version}). Please choose a lower version.`
248
+ try {
249
+ const response = await this.client.rest.get(
250
+ `/interaction/v1/interactions/key:${encodeURIComponent(key)}?extras=activities`
244
251
  );
245
- return false;
252
+ const results = this.parseResponseBody(response, key);
253
+ cachedJourney = results[key];
254
+ id = cachedJourney?.id;
255
+ } catch {
256
+ // handle below
246
257
  }
247
- Util.logger.debug(`Deleting interaction ${key} via its ID ${singleKey}`);
248
258
  }
249
- if (version !== '*') {
250
- if (!/^\d+$/.test(version)) {
251
- Util.logger.error(
252
- 'Version is required for deleting interactions to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
253
- key +
254
- '/4'
255
- );
256
- return false;
257
- }
259
+ if (!cachedJourney?.key) {
258
260
  Util.logger.warn(
259
- `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all interactions on this BU.`
261
+ ` skipping deletion of ${this.definition.type} ${key}: not found on server`
260
262
  );
263
+ return false;
261
264
  }
265
+ switch (cachedJourney.definitionType) {
266
+ case 'Multistep': {
267
+ if (version && version !== '*' && version > cachedJourney.version) {
268
+ Util.logger.error(
269
+ `The chosen version (${version}) is higher than the latest known version (${cachedJourney.version}). Please choose a lower version.`
270
+ );
271
+ return false;
272
+ }
273
+ if (version !== '*') {
274
+ if (!/^\d+$/.test(version)) {
275
+ Util.logger.error(
276
+ 'Version is required for deleting interactions to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
277
+ key +
278
+ '/4'
279
+ );
280
+ return false;
281
+ }
282
+ Util.logger.warn(
283
+ `Deleting Journeys via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all interactions on this BU.`
284
+ );
285
+ }
262
286
 
263
- return super.deleteByKeyREST(
264
- '/interaction/v1/interactions/' +
265
- singleKey +
266
- (version === '*' ? '' : `?versionNumber=${version}`),
267
- key,
268
- false
269
- );
287
+ return super.deleteByKeyREST(
288
+ '/interaction/v1/interactions/' +
289
+ id +
290
+ (version === '*' ? '' : `?versionNumber=${version}`),
291
+ key,
292
+ false
293
+ );
294
+ // break;
295
+ }
296
+ default: {
297
+ // Quicksend, Transactional dont have versions
298
+ const response = await super.deleteByKeyREST(
299
+ '/interaction/v1/interactions/' + id,
300
+ key,
301
+ false
302
+ );
303
+ if (response && cachedJourney.definitionType === 'Transactional') {
304
+ const msg = [];
305
+ if (cachedJourney.activities[0]?.configurationArguments?.triggeredSendKey) {
306
+ msg.push(
307
+ `transactionalEmail "${cachedJourney.activities[0].configurationArguments.triggeredSendKey}"`
308
+ );
309
+ }
310
+ if (msg.length) {
311
+ Util.logger.info(` - Remember to also delete linked ${msg.join(' and ')}`);
312
+ }
313
+ }
314
+ return response;
315
+ }
316
+ }
270
317
  }
271
318
 
272
319
  /**
@@ -1607,7 +1654,11 @@ class Journey extends MetadataType {
1607
1654
  );
1608
1655
  const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
1609
1656
  if (cachedVersion) {
1610
- if (cachedVersion.status === 'Draft') {
1657
+ if (
1658
+ cachedVersion.status === 'Draft' ||
1659
+ cachedVersion.definitionType !== 'Multistep'
1660
+ ) {
1661
+ // we can update journeys either if there is a draft version or if the type is not multistep. transactional and quicksend journeys do not have versions.
1611
1662
  // add version to ensure we update the correct one
1612
1663
  metadataMap[metadataKey].version = cachedVersion.version;
1613
1664
  // update modifiedDate field to bypass API-error "Another user recently modified this journey. Refresh to edit the latest version."
@@ -2335,121 +2386,406 @@ class Journey extends MetadataType {
2335
2386
  let version;
2336
2387
  const endpoint = '/interaction/v1/interactions/stop/';
2337
2388
  const stoppedKeyArr = [];
2389
+ const pauseTransactionalKeyArr = [];
2338
2390
  const apiLimit = pLimit(20);
2339
2391
  const journeyCache = await this.retrieveForCache();
2340
2392
 
2393
+ const stoppableJourneyStatus = ['Paused', 'Published', 'Finishing'];
2394
+
2341
2395
  await Promise.allSettled(
2342
2396
  keyArr.map((key) =>
2343
2397
  apiLimit(async () => {
2344
2398
  [key, version] = key.split('/');
2345
2399
  if (journeyCache.metadata[key]) {
2346
- version ||= journeyCache.metadata[key].version;
2347
- if (version === '*') {
2348
- Util.logger.warn(
2349
- ` ☇ skipping ${this.definition.type} ${key}: Cannot stop all versions at once. The highest version is ${journeyCache.metadata[key].version}`
2350
- );
2351
- return;
2352
- }
2353
- try {
2354
- await this.client.rest.post(
2355
- endpoint +
2356
- journeyCache.metadata[key].id +
2357
- `?versionNumber=${version}`,
2358
- {}
2359
- );
2360
- Util.logger.info(
2361
- ` - Stopped ${this.definition.type} ${key}/${version}`
2362
- );
2363
- stoppedKeyArr.push(key);
2364
- } catch (ex) {
2365
- Util.logger.error(
2366
- ` - Stopping ${this.definition.type} ${key} failed: ${ex.message}`
2367
- );
2400
+ switch (journeyCache.metadata[key].definitionType) {
2401
+ case 'Transactional': {
2402
+ // transactional send journeys cannot be "stopped" but only "paused"
2403
+ pauseTransactionalKeyArr.push(key);
2404
+ break;
2405
+ }
2406
+ case 'Multistep': {
2407
+ const toBeStoppedVersions = [];
2408
+ if (version === '*') {
2409
+ const responseAllVersions = await this.client.rest.getBulk(
2410
+ '/interaction/v1/interactions/?id=' +
2411
+ journeyCache.metadata[key].id +
2412
+ '&mostRecentVersionOnly=false',
2413
+ this.definition.restPageSize || 500
2414
+ );
2415
+ if (responseAllVersions?.items?.length) {
2416
+ // find all active versions
2417
+ const allActiveVersions = responseAllVersions.items
2418
+ .filter((item) =>
2419
+ stoppableJourneyStatus.includes(item.status)
2420
+ )
2421
+ .map((item) => item.version);
2422
+ if (allActiveVersions.length) {
2423
+ toBeStoppedVersions.push(...allActiveVersions);
2424
+ }
2425
+ }
2426
+ if (!toBeStoppedVersions.length) {
2427
+ Util.logger.warn(
2428
+ ` ☇ skipping stop of ${this.definition.type} ${key}: no active versions found`
2429
+ );
2430
+ }
2431
+ } else {
2432
+ if (!version) {
2433
+ version = journeyCache.metadata[key].version;
2434
+ Util.logger.info(
2435
+ Util.getGrayMsg(
2436
+ ` - ${this.definition.type} ${key}: no version provided. Will try to stop latest version: Version ${version}. To stop all versions, append /* after the key.`
2437
+ )
2438
+ );
2439
+ if (
2440
+ !stoppableJourneyStatus.includes(
2441
+ journeyCache.metadata[key].status
2442
+ )
2443
+ ) {
2444
+ Util.logger.warn(
2445
+ ` ☇ skipping stop of ${this.definition.type} ${key}: version ${version} has status "${journeyCache.metadata[key].status}" which cannot be stopped. To stop all versions, append /* after the key.`
2446
+ );
2447
+ }
2448
+ }
2449
+ toBeStoppedVersions.push(version);
2450
+ }
2451
+ const rateLimitActivities = pLimit(2);
2452
+ const stoppedVersions = (
2453
+ await Promise.all(
2454
+ toBeStoppedVersions.map((version) =>
2455
+ rateLimitActivities(async () => {
2456
+ try {
2457
+ await this.client.rest.post(
2458
+ endpoint +
2459
+ journeyCache.metadata[key].id +
2460
+ `?versionNumber=${version}`,
2461
+ {}
2462
+ );
2463
+ Util.logger.info(
2464
+ ` - Stopped ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version}`
2465
+ );
2466
+ return version;
2467
+ } catch (ex) {
2468
+ Util.logger.error(
2469
+ ` - Stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} - Version ${version} failed: ${ex.message}`
2470
+ );
2471
+ return;
2472
+ }
2473
+ })
2474
+ )
2475
+ )
2476
+ ).filter(Boolean);
2477
+ if (stoppedVersions.length === toBeStoppedVersions.length) {
2478
+ stoppedKeyArr.push(key);
2479
+ }
2480
+
2481
+ break;
2482
+ }
2483
+ default: {
2484
+ Util.logger.error(
2485
+ ` - Stopping ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Unsupported definitionType '${journeyCache.metadata[key].definitionType}'`
2486
+ );
2487
+ }
2368
2488
  }
2489
+ } else {
2490
+ Util.logger.error(
2491
+ ` ☇ skipping stop of ${this.definition.type} ${key}: not found on server`
2492
+ );
2369
2493
  }
2370
2494
  })
2371
2495
  )
2372
2496
  );
2497
+ stoppedKeyArr.push(...(await this.pause(pauseTransactionalKeyArr)));
2373
2498
 
2374
2499
  return stoppedKeyArr;
2375
2500
  }
2501
+
2376
2502
  /**
2377
2503
  * pauses selected journey versions
2378
2504
  *
2379
2505
  * @param {string[]} keyArr customerkey of the metadata
2506
+ * @param {MetadataTypeMapObj} [journeyCache] metadata cache used by refresh to avoid recaching
2380
2507
  * @returns {Promise.<string[]>} Returns list of keys that were paused
2381
2508
  */
2382
- static async pause(keyArr) {
2509
+ static async pause(keyArr, journeyCache) {
2383
2510
  let version;
2384
- const endpoint = '/interaction/v1/interactions/pause/';
2385
- const stoppedKeyArr = [];
2511
+ const pausedKeyArr = [];
2386
2512
  const apiLimit = pLimit(20);
2387
- const journeyCache = await this.retrieveForCache();
2513
+ journeyCache ||= await this.retrieveForCache();
2388
2514
 
2389
2515
  await Promise.allSettled(
2390
2516
  keyArr.map((key) =>
2391
2517
  apiLimit(async () => {
2392
2518
  [key, version] = key.split('/');
2393
2519
  if (journeyCache.metadata[key]) {
2394
- version ||= journeyCache.metadata[key].version;
2395
- try {
2396
- await this.client.rest.post(
2397
- endpoint +
2398
- journeyCache.metadata[key].id +
2399
- (version === '*'
2400
- ? '?allVersions=true'
2401
- : `?versionNumber=${version}`),
2402
- {}
2403
- );
2404
- Util.logger.info(` - Paused ${this.definition.type} ${key}/${version}`);
2405
- stoppedKeyArr.push(key);
2406
- } catch (ex) {
2520
+ if (journeyCache.metadata[key].status !== 'Published') {
2407
2521
  Util.logger.error(
2408
- ` - Pausing ${this.definition.type} ${key} failed: ${ex.message}`
2522
+ ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2409
2523
  );
2524
+ return;
2410
2525
  }
2526
+ switch (journeyCache.metadata[key].definitionType) {
2527
+ case 'Transactional': {
2528
+ try {
2529
+ const response = await this.client.rest.post(
2530
+ '/interaction/v1/interactions/transactional/pause',
2531
+ { definitionId: journeyCache.metadata[key].id }
2532
+ );
2533
+ if (response.errors?.length) {
2534
+ throw new Error(JSON.stringify(response));
2535
+ } else {
2536
+ Util.logger.info(
2537
+ ` - 🛑 paused ${this.definition.type} ${key} / ${journeyCache.metadata[key].name}`
2538
+ );
2539
+ pausedKeyArr.push(key);
2540
+ }
2541
+ } catch (ex) {
2542
+ Util.logger.error(
2543
+ ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: ${ex.message}`
2544
+ );
2545
+ }
2546
+ break;
2547
+ }
2548
+ case 'Multistep': {
2549
+ version ||= journeyCache.metadata[key].version;
2550
+ try {
2551
+ await this.client.rest.post(
2552
+ '/interaction/v1/interactions/pause/' +
2553
+ journeyCache.metadata[key].id +
2554
+ (version === '*'
2555
+ ? '?allVersions=true'
2556
+ : `?versionNumber=${version}`),
2557
+ {}
2558
+ );
2559
+ Util.logger.info(
2560
+ ` -- 🛑 paused ${this.definition.type} ${key}/${version} / ${journeyCache.metadata[key].name}`
2561
+ );
2562
+ pausedKeyArr.push(key);
2563
+ } catch (ex) {
2564
+ Util.logger.error(
2565
+ ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: ${ex.message}`
2566
+ );
2567
+ }
2568
+ break;
2569
+ }
2570
+ default: {
2571
+ Util.logger.error(
2572
+ ` - Pausing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Unsupported definitionType '${journeyCache.metadata[key].definitionType}'`
2573
+ );
2574
+ }
2575
+ }
2576
+ } else {
2577
+ Util.logger.error(
2578
+ ` ☇ skipping pause of ${this.definition.type} ${key}: not found on server`
2579
+ );
2411
2580
  }
2412
2581
  })
2413
2582
  )
2414
2583
  );
2415
2584
 
2416
- return stoppedKeyArr;
2585
+ return pausedKeyArr;
2417
2586
  }
2418
2587
  /**
2419
2588
  * resumes selected journey versions
2420
2589
  *
2421
2590
  * @param {string[]} keyArr customerkey of the metadata
2422
- * @returns {Promise.<string[]>} Returns list of keys that were paused
2591
+ * @param {MetadataTypeMapObj} [journeyCache] metadata cache used by refresh to avoid recaching
2592
+ * @returns {Promise.<string[]>} Returns list of keys that were resumed
2423
2593
  */
2424
- static async execute(keyArr) {
2594
+ static async execute(keyArr, journeyCache) {
2425
2595
  let version;
2426
2596
  const endpoint = '/interaction/v1/interactions/resume/';
2427
2597
  const resumedKeyArr = [];
2428
2598
  const apiLimit = pLimit(20);
2429
- const journeyCache = await this.retrieveForCache();
2599
+ journeyCache ||= await this.retrieveForCache();
2430
2600
 
2431
2601
  await Promise.allSettled(
2432
2602
  keyArr.map((key) =>
2433
2603
  apiLimit(async () => {
2434
2604
  [key, version] = key.split('/');
2435
2605
  if (journeyCache.metadata[key]) {
2436
- version ||= journeyCache.metadata[key].version;
2437
- try {
2438
- await this.client.rest.post(
2439
- endpoint +
2440
- journeyCache.metadata[key].id +
2441
- (version === '*'
2442
- ? '?allVersions=true'
2443
- : `?versionNumber=${version}`),
2444
- {}
2606
+ if (journeyCache.metadata[key].status !== 'Paused') {
2607
+ Util.logger.error(
2608
+ ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Cannot pause a journey in status ${journeyCache.metadata[key].status}`
2445
2609
  );
2446
- Util.logger.info(
2447
- ` - Resumed ${this.definition.type} ${key}/${version}`
2610
+ return;
2611
+ }
2612
+ switch (journeyCache.metadata[key].definitionType) {
2613
+ case 'Transactional': {
2614
+ try {
2615
+ const response = await this.client.rest.post(
2616
+ '/interaction/v1/interactions/transactional/resume',
2617
+ { definitionId: journeyCache.metadata[key].id }
2618
+ );
2619
+ if (response.errors?.length) {
2620
+ throw new Error(JSON.stringify(response));
2621
+ } else {
2622
+ Util.logger.info(
2623
+ ` - ✅ resumed ${this.definition.type} ${key} / ${journeyCache.metadata[key].name}`
2624
+ );
2625
+ resumedKeyArr.push(key);
2626
+ }
2627
+ } catch (ex) {
2628
+ Util.logger.error(
2629
+ ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: ${ex.message}`
2630
+ );
2631
+ }
2632
+ break;
2633
+ }
2634
+ case 'Multistep': {
2635
+ version ||= journeyCache.metadata[key].version;
2636
+ try {
2637
+ await this.client.rest.post(
2638
+ endpoint +
2639
+ journeyCache.metadata[key].id +
2640
+ (version === '*'
2641
+ ? '?allVersions=true'
2642
+ : `?versionNumber=${version}`),
2643
+ {}
2644
+ );
2645
+ Util.logger.info(
2646
+ ` - ✅ resumed ${this.definition.type} ${key}/${version}`
2647
+ );
2648
+ resumedKeyArr.push(key);
2649
+ } catch (ex) {
2650
+ Util.logger.error(
2651
+ ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: ${ex.message}`
2652
+ );
2653
+ }
2654
+ break;
2655
+ }
2656
+ default: {
2657
+ Util.logger.error(
2658
+ ` - Resuming ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Unsupported definitionType '${journeyCache.metadata[key].definitionType}'`
2659
+ );
2660
+ }
2661
+ }
2662
+ } else {
2663
+ Util.logger.error(
2664
+ ` ☇ skipping resume of ${this.definition.type} ${key}: not found on server`
2665
+ );
2666
+ }
2667
+ })
2668
+ )
2669
+ );
2670
+
2671
+ return resumedKeyArr;
2672
+ }
2673
+
2674
+ /**
2675
+ * TSD-specific refresh method that finds active TSDs and refreshes them
2676
+ *
2677
+ * @param {string[]} keyArr metadata keys
2678
+ * @param {boolean} [checkKey] whether to check if the key is valid
2679
+ * @returns {Promise.<string[]>} Returns list of keys that were refreshed
2680
+ */
2681
+ static async refresh(keyArr, checkKey = true) {
2682
+ console.time('Time'); // eslint-disable-line no-console
2683
+ if (!keyArr) {
2684
+ Util.logger.error('No refresh-keys provided');
2685
+ return [];
2686
+ // keyArr = await this.getKeysForValidTSDs((await this.findRefreshableItems()).metadata);
2687
+ // checkKey = false;
2688
+ }
2689
+ let journeyCache;
2690
+ if (checkKey) {
2691
+ journeyCache = await this.retrieveForCache();
2692
+ }
2693
+ // then executes pause, publish, start on them.
2694
+ Util.logger.info(`Refreshing ${keyArr.length} ${this.definition.typeName}...`);
2695
+ Util.logger.debug(`Refreshing keys: ${keyArr.join(', ')}`);
2696
+ const refreshedKeyArr = [];
2697
+ const tsKeys = [];
2698
+ const rateLimit = pLimit(10);
2699
+ await Promise.all(
2700
+ keyArr.map((key) =>
2701
+ rateLimit(async () => {
2702
+ if (checkKey && !journeyCache.metadata[key]) {
2703
+ Util.logger.error(
2704
+ ` ☇ skipping refresh of ${this.definition.type} ${key}: not found on server`
2705
+ );
2706
+ return;
2707
+ }
2708
+ switch (journeyCache.metadata[key].definitionType) {
2709
+ case 'Transactional': {
2710
+ if (checkKey && journeyCache.metadata[key]?.status !== 'Published') {
2711
+ Util.logger.error(
2712
+ ` ☇ skipping refresh of ${this.definition.type} ${key}: Can only refresh journeys with status 'Published'. Found status: ${journeyCache.metadata[key]?.status}`
2713
+ );
2714
+ } else {
2715
+ const result = await this._refreshItem(key, journeyCache);
2716
+ if (result) {
2717
+ refreshedKeyArr.push(key);
2718
+ }
2719
+ }
2720
+ break;
2721
+ }
2722
+ case 'Multistep': {
2723
+ // find all published & paused versions
2724
+ const responseAllVersions = await this.client.rest.getBulk(
2725
+ '/interaction/v1/interactions/?id=' +
2726
+ journeyCache.metadata[key].id +
2727
+ '&mostRecentVersionOnly=false',
2728
+ this.definition.restPageSize || 500
2448
2729
  );
2449
- resumedKeyArr.push(key);
2450
- } catch (ex) {
2730
+ if (responseAllVersions?.items?.length) {
2731
+ const allActiveVersions = responseAllVersions.items
2732
+ .filter(
2733
+ (item) =>
2734
+ item.status === 'Paused' || item.status === 'Published'
2735
+ )
2736
+ .map((item) => item.version);
2737
+ if (allActiveVersions.length) {
2738
+ Util.logger.info(
2739
+ Util.getGrayMsg(
2740
+ ` - journey ${key} / ${journeyCache.metadata[key].name} Paused/Published version numbers: ` +
2741
+ allActiveVersions.join(', ')
2742
+ )
2743
+ );
2744
+ // get TS keys from email activities of paused/published versions
2745
+ const rateLimitActivities = pLimit(2);
2746
+ tsKeys.push(
2747
+ ...(
2748
+ await Promise.all(
2749
+ allActiveVersions.map((version) =>
2750
+ rateLimitActivities(async () => {
2751
+ const journey = await this.client.rest.get(
2752
+ '/interaction/v1/interactions/' +
2753
+ journeyCache.metadata[key]?.id +
2754
+ '?extras=activities&versionNumber=' +
2755
+ version
2756
+ );
2757
+ // return all triggeredSends
2758
+ // ! if somebody changed the key of the triggeredSend then the journey would have wrong info in triggeredSendKey. There is the alternative field triggeredSendId but that would be too costly to use here because we would need to retrieve all TSs to find the correct one. Also, changing TS keys is not a common practice.
2759
+ return journey.activities
2760
+ .filter(
2761
+ (activity) =>
2762
+ activity.type === 'EMAILV2' &&
2763
+ activity.configurationArguments
2764
+ ?.triggeredSendKey
2765
+ )
2766
+ .map(
2767
+ (activity) =>
2768
+ activity.configurationArguments
2769
+ ?.triggeredSendKey
2770
+ );
2771
+ })
2772
+ )
2773
+ )
2774
+ ).flat()
2775
+ );
2776
+
2777
+ refreshedKeyArr.push(key);
2778
+ } else {
2779
+ Util.logger.error(
2780
+ ` ☇ skipping refresh of ${this.definition.type} ${key}: no published/paused versions found`
2781
+ );
2782
+ }
2783
+ }
2784
+ break;
2785
+ }
2786
+ default: {
2451
2787
  Util.logger.error(
2452
- ` - Resuming ${this.definition.type} ${key} failed: ${ex.message}`
2788
+ ` - Refreshing ${this.definition.type} ${key} / ${journeyCache.metadata[key].name} failed: Unsupported definitionType '${journeyCache.metadata[key].definitionType}'`
2453
2789
  );
2454
2790
  }
2455
2791
  }
@@ -2457,7 +2793,49 @@ class Journey extends MetadataType {
2457
2793
  )
2458
2794
  );
2459
2795
 
2460
- return resumedKeyArr;
2796
+ if (tsKeys.length) {
2797
+ // refresh TriggeredSends
2798
+ TriggeredSend.buObject = this.buObject;
2799
+ TriggeredSend.client = this.client;
2800
+ TriggeredSend.properties = this.properties;
2801
+ // hard-refresh all triggeredSends even if the TS was paused (inactive) before
2802
+ await TriggeredSend.refresh(tsKeys, false);
2803
+ } else {
2804
+ Util.logger.info(Util.getGrayMsg('No triggeredSends found to refresh'));
2805
+ }
2806
+
2807
+ Util.logger.info(
2808
+ `Refreshed ${refreshedKeyArr.length} of ${keyArr.length} ${this.definition.type}`
2809
+ );
2810
+ console.timeEnd('Time'); // eslint-disable-line no-console
2811
+ return refreshedKeyArr;
2812
+ }
2813
+ /**
2814
+ * helper for {@link Journey.refresh} that pauses, publishes and starts a triggered send
2815
+ *
2816
+ * @param {string} key external key of triggered send item
2817
+ * @param {MetadataTypeMapObj} journeyCache metadata cache
2818
+ * @returns {Promise.<boolean>} true if refresh was successful
2819
+ */
2820
+ static async _refreshItem(key, journeyCache) {
2821
+ // pause
2822
+ const pausedKeys = await this.pause([key], journeyCache);
2823
+ if (!pausedKeys?.length || pausedKeys[0] !== key) {
2824
+ Util.logger.error(` - failed to pause ${this.definition.typeName}: ${key}`);
2825
+ return false;
2826
+ }
2827
+
2828
+ // update cache or else resume (execute) will fail
2829
+ journeyCache.metadata[key].status = 'Paused';
2830
+
2831
+ // resume
2832
+ const resumedKeys = await this.execute([key], journeyCache);
2833
+ if (!resumedKeys?.length || resumedKeys[0] !== key) {
2834
+ Util.logger.error(` - failed to resume ${this.definition.typeName}: ${key}`);
2835
+ return false;
2836
+ }
2837
+
2838
+ return true;
2461
2839
  }
2462
2840
  }
2463
2841