mcdev 7.10.0 → 7.10.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 (98) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/@types/lib/index.d.ts +4 -4
  3. package/@types/lib/index.d.ts.map +1 -1
  4. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  5. package/@types/lib/metadataTypes/Automation.d.ts +62 -51
  6. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/DataExtract.d.ts +13 -2
  8. package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/Event.d.ts +8 -0
  10. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/MetadataType.d.ts +19 -3
  14. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/MobileMessage.d.ts.map +1 -1
  16. package/@types/lib/metadataTypes/SenderProfile.d.ts +3 -1
  17. package/@types/lib/metadataTypes/SenderProfile.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +37 -1
  19. package/@types/lib/metadataTypes/definitions/DataExtract.definition.d.ts +21 -3
  20. package/@types/lib/metadataTypes/definitions/SenderProfile.definition.d.ts +3 -1
  21. package/@types/lib/util/util.d.ts +1 -0
  22. package/@types/lib/util/util.d.ts.map +1 -1
  23. package/@types/lib/util/validations.d.ts.map +1 -1
  24. package/@types/types/mcdev.d.d.ts +32 -0
  25. package/@types/types/mcdev.d.d.ts.map +1 -1
  26. package/lib/index.js +14 -7
  27. package/lib/metadataTypes/Asset.js +4 -5
  28. package/lib/metadataTypes/Automation.js +202 -399
  29. package/lib/metadataTypes/DataExtension.js +1 -1
  30. package/lib/metadataTypes/DataExtract.js +41 -6
  31. package/lib/metadataTypes/DomainVerification.js +1 -1
  32. package/lib/metadataTypes/EmailSend.js +3 -3
  33. package/lib/metadataTypes/Event.js +39 -20
  34. package/lib/metadataTypes/FileTransfer.js +5 -5
  35. package/lib/metadataTypes/ImportFile.js +9 -7
  36. package/lib/metadataTypes/Journey.js +4 -9
  37. package/lib/metadataTypes/MetadataType.js +80 -58
  38. package/lib/metadataTypes/MobileKeyword.js +2 -2
  39. package/lib/metadataTypes/MobileMessage.js +4 -2
  40. package/lib/metadataTypes/Query.js +5 -5
  41. package/lib/metadataTypes/Script.js +5 -5
  42. package/lib/metadataTypes/SendClassification.js +3 -3
  43. package/lib/metadataTypes/SenderProfile.js +3 -3
  44. package/lib/metadataTypes/TransactionalEmail.js +1 -1
  45. package/lib/metadataTypes/TransactionalMessage.js +1 -1
  46. package/lib/metadataTypes/TriggeredSend.js +3 -3
  47. package/lib/metadataTypes/Verification.js +1 -1
  48. package/lib/metadataTypes/definitions/Automation.definition.js +43 -7
  49. package/lib/metadataTypes/definitions/DataExtract.definition.js +16 -3
  50. package/lib/metadataTypes/definitions/DomainVerification.definition.js +1 -1
  51. package/lib/metadataTypes/definitions/SenderProfile.definition.js +3 -1
  52. package/lib/util/util.js +12 -0
  53. package/lib/util/validations.js +3 -1
  54. package/package.json +1 -1
  55. package/test/general.test.js +39 -34
  56. package/test/mockRoot/.mcdevrc.json +1 -1
  57. package/test/resourceFactory.js +8 -5
  58. package/test/resources/9999999/automation/create-expected.json +2 -1
  59. package/test/resources/9999999/automation/retrieve-expected.json +7 -1
  60. package/test/resources/9999999/automation/retrieve-wait-expected.json +7 -1
  61. package/test/resources/9999999/automation/update-expected.json +24 -2
  62. package/test/resources/9999999/automation/update-testExisting_automation-expected.md +5 -2
  63. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/patch-response.json +1 -0
  64. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/patch-response.json +1 -0
  65. package/test/resources/9999999/dataExtract/build-expected.json +2 -2
  66. package/test/resources/9999999/dataExtract/get-expected.json +2 -2
  67. package/test/resources/9999999/dataExtract/patch-expected.json +2 -2
  68. package/test/resources/9999999/dataExtract/post-expected.json +2 -2
  69. package/test/resources/9999999/dataExtract/template-expected.json +2 -2
  70. package/test/resources/9999999/legacy/v1/beta/automations/notifications/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow-PAUSED/get-response.json +3 -0
  71. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/NewRkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/get-response.json +29 -0
  72. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/get-response.json +50 -0
  73. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow-PAUSED/get-response.json +51 -0
  74. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/bHF6Q0Q3b1VXa21OdVQzZFQ0ckVSQToyNTow/get-response.json +7 -1
  75. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/cDhLQ2o2NExxVVc5N3VZeHF5WEExUToyNTow/get-response.json +79 -0
  76. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +61 -2
  77. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/post-response-pauseSchedule.json +1 -0
  78. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/post-response-schedule.json +1 -0
  79. package/test/resources/9999999/messaging/v1/domainverification/get-response.json +10 -1
  80. package/test/resources/9999999/senderProfile/build-expected.json +1 -1
  81. package/test/resources/9999999/senderProfile/get-expected.json +1 -1
  82. package/test/resources/9999999/senderProfile/patch-expected.json +1 -1
  83. package/test/resources/9999999/senderProfile/retrieve-response.xml +1 -1
  84. package/test/resources/9999999/senderProfile/template-expected.json +1 -1
  85. package/test/type.automation.test.js +18 -17
  86. package/test/type.dataExtract.test.js +4 -4
  87. package/test/type.domainVerification.test.js +3 -3
  88. package/test/type.journey.test.js +11 -4
  89. package/test/type.query.test.js +2 -2
  90. package/test/type.script.test.js +1 -1
  91. package/test/type.senderProfile.test.js +20 -3
  92. package/test/type.triggeredSend.test.js +13 -1
  93. package/types/mcdev.d.js +8 -0
  94. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +0 -52
  95. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml +0 -38
  96. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_pause-response.xml +0 -52
  97. package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +0 -52
  98. package/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +0 -52
@@ -146,12 +146,11 @@ class Automation extends MetadataType {
146
146
  * helper for {@link Automation.retrieve} to get Automation Notifications
147
147
  *
148
148
  * @param {MetadataTypeMap} metadataMap keyField => metadata map
149
+ * @param {boolean} [skipNotification] skip notification retrieval
149
150
  * @returns {Promise.<object>} Promise of automation legacy api response
150
151
  */
151
- static async #getAutomationLegacyREST(metadataMap) {
152
- Util.logger.info(
153
- Util.getGrayMsg(` Retrieving Automation Notification & wait information...`)
154
- );
152
+ static async #getAutomationLegacyREST(metadataMap, skipNotification = false) {
153
+ Util.logger.info(Util.getGrayMsg(` Retrieving additional automation details...`));
155
154
 
156
155
  // get list of keys that we retrieved so far
157
156
  const foundKeys = Object.keys(metadataMap);
@@ -172,27 +171,9 @@ class Automation extends MetadataType {
172
171
  id: automationLegacyMapObj.metadata[key].id,
173
172
  key,
174
173
  }));
175
- // wait
176
- const waitLegacyMap = Object.keys(automationLegacyMapObj.metadata)
174
+ // created / modified / paused / wait activities
175
+ const extendedDetailsLegacyMap = Object.keys(automationLegacyMapObj.metadata)
177
176
  .filter((key) => foundKeys.includes(key))
178
- .filter((key) => {
179
- const steps = automationLegacyMapObj.metadata[key].processes;
180
- // wait activities
181
- if (!Array.isArray(steps)) {
182
- return false;
183
- }
184
- for (const step of steps) {
185
- if (!Array.isArray(step.workerCounts)) {
186
- continue;
187
- }
188
- for (const activity of step.workerCounts) {
189
- if (activity.objectTypeId === 467) {
190
- return true;
191
- }
192
- }
193
- }
194
- return false;
195
- })
196
177
  .map((key) => ({
197
178
  id: automationLegacyMapObj.metadata[key].id,
198
179
  key,
@@ -202,31 +183,61 @@ class Automation extends MetadataType {
202
183
 
203
184
  // get wait activities for automations using it
204
185
  await Promise.all(
205
- waitLegacyMap.map((automationLegacy) =>
186
+ extendedDetailsLegacyMap.map((automationLegacy) =>
206
187
  // notifications
207
188
  rateLimit(async () => {
208
189
  // this is a file so extended is at another endpoint
209
190
  try {
210
- const waitResult = await this.client.rest.get(
191
+ /** @type {AutomationItem} */
192
+ const item = metadataMap[automationLegacy.key];
193
+ item.legacyId = automationLegacy.id;
194
+
195
+ const extended = await this.client.rest.get(
211
196
  `/legacy/v1/beta/bulk/automations/automation/definition/` +
212
197
  automationLegacy.id
213
198
  );
214
- if (Array.isArray(waitResult?.processes)) {
215
- const steps = waitResult.processes;
216
- // wait activities
217
- for (const step of steps) {
199
+ // set those for {@link schedule}
200
+ item.type ||= extended.automationType;
201
+ item.status ||= extended.status;
202
+
203
+ // created
204
+ item.createdName = extended.createdBy?.name;
205
+ item.createdDate = extended.createdDate;
206
+
207
+ // last modified
208
+ item.modifiedName = extended.lastSavedBy?.name;
209
+ item.modifiedDate = extended.lastSaveDate;
210
+
211
+ // last paused
212
+ item.pausedName = extended.lastPausedBy?.name;
213
+ item.pausedDate = extended.lastPausedDate;
214
+
215
+ // schedule id for activating the schedule
216
+ if (extended?.scheduleObject?.id && item.schedule) {
217
+ // save schedule id in cached metadata for retrieval during scheduling
218
+ item.schedule.id = extended.scheduleObject.id;
219
+ }
220
+
221
+ // add timezone to wait activities
222
+ if (Array.isArray(extended?.processes)) {
223
+ for (const step of extended.processes) {
224
+ // steps
225
+ if (!Array.isArray(step?.workers)) {
226
+ continue;
227
+ }
218
228
  for (const activity of step.workers) {
229
+ // activties
219
230
  if (
220
231
  activity.objectTypeId === 467 &&
221
232
  activity.serializedObject
222
233
  ) {
234
+ // wait activities
223
235
  const waitObj = JSON.parse(activity.serializedObject);
224
236
  if (waitObj.timeZone) {
225
237
  // add timezone to the wait activity
226
- metadataMap[automationLegacy.key].steps[
227
- step.sequence
228
- ].activities[activity.sequence].timeZone =
229
- waitObj.timeZone;
238
+ item.steps[step.sequence].activities[
239
+ activity.sequence
240
+ ].timeZone = waitObj.timeZone;
230
241
  }
231
242
  // * wait activities are not supported in the new API
232
243
  }
@@ -235,13 +246,16 @@ class Automation extends MetadataType {
235
246
  }
236
247
  } catch (ex) {
237
248
  Util.logger.debug(
238
- ` ☇ issue retrieving wait details for automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
249
+ ` ☇ issue retrieving extended details for automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
239
250
  );
240
251
  }
241
252
  })
242
253
  )
243
254
  );
244
255
 
256
+ if (skipNotification) {
257
+ return;
258
+ }
245
259
  // get notifications for each automation
246
260
  let found = 0;
247
261
  let skipped = 0;
@@ -644,220 +658,179 @@ class Automation extends MetadataType {
644
658
  return null;
645
659
  }
646
660
  }
647
-
648
661
  /**
649
- * a function to start query execution via API
662
+ * a function to active the schedule of an automation
650
663
  *
651
664
  * @param {string[]} keyArr customerkey of the metadata
652
665
  * @returns {Promise.<string[]>} Returns list of keys that were executed
653
666
  */
654
- static async execute(keyArr) {
655
- /** @type {AutomationMap} */
667
+ static async schedule(keyArr) {
668
+ return this.#schedulePause('schedule', keyArr);
669
+ }
670
+ /**
671
+ * a function to pause the schedule of an automation
672
+ *
673
+ * @param {string[]} keyArr customerkey of the metadata
674
+ * @returns {Promise.<string[]>} Returns list of keys that were executed
675
+ */
676
+ static async pause(keyArr) {
677
+ return this.#schedulePause('pause', keyArr);
678
+ }
679
+ /**
680
+ * a function to active the schedule of an automation
681
+ *
682
+ * @param {'schedule'|'pause'} mode what to do
683
+ * @param {string[]} keyArr customerkey of the metadata
684
+ * @returns {Promise.<string[]>} Returns list of keys that were executed
685
+ */
686
+ static async #schedulePause(mode, keyArr) {
656
687
  const metadataMap = {};
657
688
  for (const key of keyArr) {
658
- if (Util.OPTIONS.schedule) {
659
- // schedule
660
- const results = await this.retrieve(undefined, undefined, undefined, key);
661
- if (Object.keys(results.metadata).length) {
662
- for (const resultKey of Object.keys(results.metadata)) {
663
- if (this.#isValidSchedule(results.metadata[resultKey])) {
664
- metadataMap[resultKey] = results.metadata[resultKey];
665
- } else {
666
- Util.logger.error(
667
- ` - skipping ${this.definition.type} ${results.metadata[resultKey].name}: no valid schedule settings found.`
668
- );
669
- }
670
- }
671
- }
672
- } else {
673
- // runOnce
674
- const objectId = await this.#getObjectIdForSingleRetrieve(key);
675
- /** @type {AutomationItem} */
676
- metadataMap[key] = { key, id: objectId };
689
+ metadataMap[key] = { key, schedule: {} };
690
+ }
691
+ await this.#getAutomationLegacyREST(metadataMap, true);
692
+ for (const key of keyArr) {
693
+ const item = metadataMap[key];
694
+ if (!item.type) {
695
+ Util.logger.error(
696
+ ` ☇ skipping ${Util.getTypeKeyName(this.definition, item)}: automation not found.`
697
+ );
698
+ delete metadataMap[key];
699
+ } else if (item.type !== 'scheduled') {
700
+ Util.logger.error(
701
+ ` ☇ skipping ${Util.getTypeKeyName(this.definition, item)}: cannot ${mode} an automation of type '${item.type}'.`
702
+ );
703
+ delete metadataMap[key];
677
704
  }
678
705
  }
679
706
  if (!Object.keys(metadataMap).length) {
680
- Util.logger.error(`No ${this.definition.type} to execute`);
681
- return;
707
+ Util.logger.error(`No ${this.definition.type} to ` + mode);
708
+ return [];
682
709
  }
683
- Util.logger.info(
684
- `Starting automations ${
685
- Util.OPTIONS.schedule
686
- ? 'according to schedule'
687
- : 'to run once (use --schedule or --execute=schedule to schedule instead)'
688
- }: ${Object.keys(metadataMap).length}`
689
- );
710
+ Util.logger.info(`${mode === 'schedule' ? 'Activating' : 'Pausing'} automations`);
690
711
  const promiseResults = [];
691
712
  for (const key of Object.keys(metadataMap)) {
692
- if (Util.OPTIONS.schedule && metadataMap[key].status === 'Scheduled') {
713
+ /** @type {AutomationItem} */
714
+ const item = metadataMap[key];
715
+ if (item.status === (mode === 'schedule' ? 'Scheduled' : 'PausedSchedule')) {
693
716
  // schedule
694
717
  Util.logger.info(
695
- ` - skipping ${this.definition.type} ${metadataMap[key].name}: already scheduled.`
718
+ ` skipping ${Util.getTypeKeyName(this.definition, item)}: already ${mode === 'schedule' ? 'activated' : 'paused'}.`
696
719
  );
697
720
  } else {
698
- // schedule + runOnce
699
- promiseResults.push(this.#executeItem(metadataMap, key));
721
+ // schedule
722
+ promiseResults.push(
723
+ this.#schedulePauseItem(mode, key, item.legacyId, item.schedule.id)
724
+ );
700
725
  }
701
726
  }
702
727
  const results = await Promise.all(promiseResults);
703
- const executedKeyArr = results
728
+ const updatedKeyArr = results
704
729
  .filter(Boolean)
705
- .filter((r) => r.response.OverallStatus === 'OK')
730
+ .filter((r) => r.response.id)
706
731
  .map((r) => r.key);
707
- Util.logger.info(`Executed ${executedKeyArr.length} of ${keyArr.length} items`);
708
- return executedKeyArr;
709
- }
732
+ Util.logger.info(
733
+ `${mode === 'schedule' ? 'Activated' : 'Paused'} ${updatedKeyArr.length} of ${keyArr.length} items`
734
+ );
710
735
 
711
- /**
712
- * helper for {@link Automation.execute}
713
- *
714
- * @param {AutomationMap} metadataMap map of metadata
715
- * @param {string} key key of the metadata
716
- * @returns {Promise.<{key:string, response:object}>} metadata key and API response
717
- */
718
- static async #executeItem(metadataMap, key) {
719
- if (Util.OPTIONS.schedule) {
720
- this.#preDeploySchedule(metadataMap[key]);
721
- metadataMap[key].status = 'Scheduled';
722
- return this.#scheduleAutomation(metadataMap, metadataMap, key);
723
- } else {
724
- return this.#runOnce(metadataMap[key]);
725
- }
736
+ return updatedKeyArr;
726
737
  }
727
738
 
728
739
  /**
729
- * helper for {@link Automation.execute}
740
+ * helper for {@link Automation.schedule}
730
741
  *
731
- * @param {AutomationItem} metadataEntry metadata object
742
+ * @param {'schedule'|'pause'} mode what to do
743
+ * @param {string} key automation key
744
+ * @param {string} automationLegacyId automation id
745
+ * @param {string} [scheduleLegacyId] schedule id
732
746
  * @returns {Promise.<{key:string, response:object}>} metadata key and API response
733
747
  */
734
- static async #runOnce(metadataEntry) {
735
- return super.executeSOAP(metadataEntry);
736
- }
737
-
738
- /**
739
- * Standardizes a check for multiple messages but adds query specific filters to error texts
740
- *
741
- * @param {object} ex response payload from REST API
742
- * @returns {string[]} formatted Error Message
743
- */
744
- static getErrorsREST(ex) {
745
- const errors = super.getErrorsREST(ex);
746
- if (errors?.length > 0) {
747
- return errors.map((msg) =>
748
- msg
749
- .split('403 Forbidden')
750
- .join('403 Forbidden: Please check if the automation is currently running.')
748
+ static async #schedulePauseItem(mode, key, automationLegacyId, scheduleLegacyId) {
749
+ if (!scheduleLegacyId) {
750
+ const extended = await this.client.rest.get(
751
+ `/legacy/v1/beta/bulk/automations/automation/definition/` + automationLegacyId
752
+ );
753
+ if (extended.scheduleObject?.id) {
754
+ scheduleLegacyId = extended.scheduleObject.id;
755
+ } else {
756
+ Util.logger.error(
757
+ ` ☇ skipping ${this.definition.type} ${key}: no valid schedule settings found.`
758
+ );
759
+ return null;
760
+ }
761
+ }
762
+ const response = await this.client.rest.post(
763
+ '/legacy/v1/beta/bulk/automations/automation/definition/?action=' +
764
+ (mode === 'schedule' ? 'schedule' : 'pauseSchedule'),
765
+ {
766
+ id: automationLegacyId,
767
+ scheduleObject: {
768
+ id: scheduleLegacyId,
769
+ },
770
+ }
771
+ );
772
+ if (response?.id === automationLegacyId) {
773
+ const item = {};
774
+ item[this.definition.keyField] = key;
775
+ Util.logger.info(
776
+ ` - ${mode === 'schedule' ? '✅ activated' : '🛑 paused'} scheduled ${Util.getTypeKeyName(this.definition, item)}`
751
777
  );
752
778
  }
753
- return errors;
754
- }
755
779
 
780
+ return { key, response };
781
+ }
756
782
  /**
757
783
  * a function to start query execution via API
758
784
  *
759
785
  * @param {string[]} keyArr customerkey of the metadata
760
- * @returns {Promise.<string[]>} Returns list of keys that were paused
786
+ * @returns {Promise.<string[]>} Returns list of keys that were executed
761
787
  */
762
- static async pause(keyArr) {
788
+ static async execute(keyArr) {
789
+ /** @type {AutomationMap} */
763
790
  const metadataMap = {};
764
791
  for (const key of keyArr) {
765
- if (key) {
766
- const results = await this.retrieve(undefined, undefined, undefined, key);
767
- if (Object.keys(results.metadata).length) {
768
- for (const key of Object.keys(results.metadata)) {
769
- if (this.#isValidSchedule(results.metadata[key])) {
770
- metadataMap[key] = results.metadata[key];
771
- } else {
772
- Util.logger.error(
773
- ` - skipping ${this.definition.type} ${results.metadata[key].name}: no valid schedule settings found.`
774
- );
775
- }
776
- }
777
- }
778
- }
792
+ // runOnce
793
+ const objectId = await this.#getObjectIdForSingleRetrieve(key);
794
+ /** @type {AutomationItem} */
795
+ metadataMap[key] = { key, id: objectId };
779
796
  }
780
-
781
- Util.logger.info(`Pausing automations: ${Object.keys(metadataMap).length}`);
797
+ if (!Object.keys(metadataMap).length) {
798
+ Util.logger.error(`No ${this.definition.type} to execute`);
799
+ return;
800
+ }
801
+ Util.logger.info(
802
+ `Starting automations to run once (use --schedule or --execute=schedule to schedule instead): ${Object.keys(metadataMap).length}`
803
+ );
782
804
  const promiseResults = [];
783
805
  for (const key of Object.keys(metadataMap)) {
784
- if (metadataMap[key].status === 'Scheduled') {
785
- promiseResults.push(this.#pauseItem(metadataMap[key]));
786
- } else if (metadataMap[key].status === 'PausedSchedule') {
787
- Util.logger.info(
788
- ` - skipping ${this.definition.type} ${metadataMap[key].name}: already paused.`
789
- );
790
- } else {
791
- Util.logger.error(
792
- ` - skipping ${this.definition.type} ${
793
- metadataMap[key].name
794
- }: currently ${metadataMap[
795
- key
796
- ].status.toLowerCase()}. Please try again in a few minutes.`
797
- );
798
- }
806
+ // schedule + runOnce
807
+ promiseResults.push(super.executeSOAP(metadataMap[key]));
799
808
  }
800
- const pausedKeyArr = (await Promise.all(promiseResults))
809
+ const results = await Promise.all(promiseResults);
810
+ const executedKeyArr = results
801
811
  .filter(Boolean)
802
812
  .filter((r) => r.response.OverallStatus === 'OK')
803
813
  .map((r) => r.key);
804
-
805
- Util.logger.info(`Paused ${pausedKeyArr.length} of ${keyArr.length} items`);
806
- return pausedKeyArr;
814
+ Util.logger.info(`Executed ${executedKeyArr.length} of ${keyArr.length} items`);
815
+ return executedKeyArr;
807
816
  }
808
817
 
809
818
  /**
810
- * helper for {@link Automation.pause}
819
+ * Standardizes a check for multiple messages but adds query specific filters to error texts
811
820
  *
812
- * @param {AutomationItem} metadata automation metadata
813
- * @returns {Promise.<{key:string, response:object}>} metadata key and API response
821
+ * @param {object} ex response payload from REST API
822
+ * @returns {string[]} formatted Error Message
814
823
  */
815
- static async #pauseItem(metadata) {
816
- const schedule = {};
817
- try {
818
- const response = await this.client.soap.schedule(
819
- 'Automation',
820
- schedule,
821
- {
822
- Interaction: {
823
- ObjectID: metadata[this.definition.idField],
824
- },
825
- },
826
- 'pause',
827
- {}
828
- );
829
- Util.logger.info(
830
- ` - paused ${this.definition.type}: ${metadata[this.definition.keyField]} / ${
831
- metadata[this.definition.nameField]
832
- }`
824
+ static getErrorsREST(ex) {
825
+ const errors = super.getErrorsREST(ex);
826
+ if (errors?.length > 0) {
827
+ return errors.map((msg) =>
828
+ msg
829
+ .split('403 Forbidden')
830
+ .join('403 Forbidden: Please check if the automation is currently running.')
833
831
  );
834
- return { key: metadata[this.definition.keyField], response };
835
- } catch (ex) {
836
- this._handleSOAPErrors(ex, 'pausing', metadata, false);
837
- return null;
838
832
  }
839
- }
840
-
841
- /**
842
- * Deploys automation - the saved file is the original one due to large differences required for deployment
843
- *
844
- * @param {AutomationMap} metadata metadata mapped by their keyField
845
- * @param {string} targetBU name/shorthand of target businessUnit for mapping
846
- * @param {string} retrieveDir directory where metadata after deploy should be saved
847
- * @returns {Promise.<AutomationMap>} Promise
848
- */
849
- static async deploy(metadata, targetBU, retrieveDir) {
850
- const upsertResults = await this.upsert(metadata, targetBU);
851
- const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
852
- if (
853
- this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
854
- !this.definition.documentInOneFile
855
- ) {
856
- const count = Object.keys(savedMetadata).length;
857
- Util.logger.debug(` - Running document for ${count} record${count === 1 ? '' : 's'}`);
858
- await this.document(savedMetadata);
859
- }
860
- return upsertResults;
833
+ return errors;
861
834
  }
862
835
 
863
836
  /**
@@ -965,7 +938,7 @@ class Automation extends MetadataType {
965
938
 
966
939
  this.#preDeploySchedule(metadata);
967
940
  // * run _buildSchedule here but only to check if things look ok - do not use the returned schedule object for deploy
968
- this._buildSchedule(metadata.schedule);
941
+ this._checkSchedule(metadata.schedule);
969
942
 
970
943
  delete metadata.schedule.timezoneName;
971
944
  delete metadata.startSource.schedule.timezoneName;
@@ -1144,10 +1117,9 @@ class Automation extends MetadataType {
1144
1117
  * Gets executed after deployment of metadata type
1145
1118
  *
1146
1119
  * @param {AutomationMap} metadataMap metadata mapped by their keyField
1147
- * @param {AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
1148
1120
  * @returns {Promise.<void>} -
1149
1121
  */
1150
- static async postDeployTasks(metadataMap, originalMetadataMap) {
1122
+ static async postDeployTasks(metadataMap) {
1151
1123
  for (const key in metadataMap) {
1152
1124
  const item = metadataMap[key];
1153
1125
 
@@ -1156,8 +1128,12 @@ class Automation extends MetadataType {
1156
1128
 
1157
1129
  if (!item.type) {
1158
1130
  // create response does not return the type attribute
1159
-
1160
- const scheduleHelper = item.schedule || item.startSource.schedule;
1131
+ if (item.startSource?.schedule) {
1132
+ // rewrite upsert reponse into retrieve format
1133
+ item.schedule = item.startSource.schedule;
1134
+ delete item.startSource;
1135
+ }
1136
+ const scheduleHelper = item.schedule;
1161
1137
 
1162
1138
  // el.type
1163
1139
  item.type = scheduleHelper
@@ -1179,7 +1155,13 @@ class Automation extends MetadataType {
1179
1155
  item.status ||= Util.inverseGet(this.definition.statusMapping, item.statusId);
1180
1156
  }
1181
1157
  // need to put schedule on here if status is scheduled
1182
- await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey);
1158
+ if (Util.OPTIONS.schedule) {
1159
+ await Automation.#schedulePauseItem(
1160
+ 'schedule',
1161
+ oldKey || key,
1162
+ metadataMap[key]?.legacyId
1163
+ );
1164
+ }
1183
1165
 
1184
1166
  // need to update notifications separately if there are any
1185
1167
  await Automation.#updateNotificationInfoREST(metadataMap, key);
@@ -1192,10 +1174,24 @@ class Automation extends MetadataType {
1192
1174
  }
1193
1175
  }
1194
1176
  }
1195
- if (Util.OPTIONS.execute || Util.OPTIONS.schedule) {
1177
+
1178
+ if (Util.OPTIONS.execute) {
1196
1179
  Util.logger.info(`Executing: ${this.definition.type}`);
1197
1180
  await this.execute(Object.keys(metadataMap));
1198
1181
  }
1182
+ Util.logger.debug(
1183
+ `Caching all ${this.definition.type} post-deploy to ensure we have all fields`
1184
+ );
1185
+
1186
+ // post-deploy re-retrieve
1187
+ // dont use retrieveForCache here because that is simplified for automations
1188
+ const typeCache = await this.retrieve();
1189
+ // update values in upsertResults with retrieved values before saving to disk
1190
+ for (const key of Object.keys(metadataMap)) {
1191
+ if (typeCache.metadata[key]) {
1192
+ metadataMap[key] = typeCache.metadata[key];
1193
+ }
1194
+ }
1199
1195
  }
1200
1196
 
1201
1197
  /**
@@ -1245,90 +1241,6 @@ class Automation extends MetadataType {
1245
1241
  }
1246
1242
  }
1247
1243
 
1248
- /**
1249
- * helper for {@link Automation.postDeployTasks}
1250
- *
1251
- * @param {AutomationMap} metadataMap metadata mapped by their keyField
1252
- * @param {AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
1253
- * @param {string} key current customer key
1254
- * @param {string} [oldKey] old customer key before fixKey / changeKeyValue / changeKeyField
1255
- * @returns {Promise.<{key:string, response:object}>} metadata key and API response
1256
- */
1257
- static async #scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey) {
1258
- let response = null;
1259
- oldKey ||= key;
1260
- if (originalMetadataMap[oldKey]?.type === 'scheduled') {
1261
- // Starting Source == 'Schedule': Try starting the automation
1262
- if (originalMetadataMap[oldKey].status === 'Scheduled') {
1263
- let schedule = null;
1264
- try {
1265
- schedule = this._buildSchedule(originalMetadataMap[oldKey].schedule);
1266
- } catch (ex) {
1267
- Util.logger.error(
1268
- `- Could not create schedule for automation '${originalMetadataMap[oldKey].name}' to start it: ${ex.message}`
1269
- );
1270
- }
1271
- if (schedule !== null) {
1272
- try {
1273
- // remove the fields that are not needed for the schedule but only for CLI output
1274
- const schedule_StartDateTime = schedule._StartDateTime;
1275
- delete schedule._StartDateTime;
1276
- const schedule_interval = schedule._interval;
1277
- delete schedule._interval;
1278
- const schedule_timezoneString = schedule._timezoneString;
1279
- delete schedule._timezoneString;
1280
- // start the automation
1281
- response = await this.client.soap.schedule(
1282
- 'Automation',
1283
- schedule,
1284
- {
1285
- Interaction: {
1286
- ObjectID: metadataMap[key][this.definition.idField],
1287
- },
1288
- },
1289
- 'start',
1290
- {}
1291
- );
1292
- const intervalString =
1293
- (schedule_interval > 1 ? `${schedule_interval} ` : '') +
1294
- (schedule.RecurrenceType === 'Daily'
1295
- ? 'Day'
1296
- : schedule.RecurrenceType.slice(0, -2) +
1297
- (schedule_interval > 1 ? 's' : ''));
1298
- Util.logger.warn(
1299
- ` - scheduled automation '${
1300
- originalMetadataMap[oldKey].name
1301
- }' deployed as Active: runs every ${intervalString} starting ${
1302
- schedule_StartDateTime.split('T').join(' ').split('.')[0]
1303
- } ${schedule_timezoneString}`
1304
- );
1305
- } catch {
1306
- // API does not return anything usefull here. We have to know the rules instead
1307
- Util.logger.error(
1308
- ` ☇ error starting scheduled ${this.definition.type}${key}: Please check schedule settings`
1309
- );
1310
- }
1311
- }
1312
- } else {
1313
- Util.logger.info(
1314
- Util.getGrayMsg(
1315
- ` - scheduled automation '${originalMetadataMap[oldKey].name}' deployed as Paused`
1316
- )
1317
- );
1318
- }
1319
- }
1320
- if (metadataMap[key].startSource) {
1321
- metadataMap[key].schedule = metadataMap[key].startSource.schedule;
1322
-
1323
- delete metadataMap[key].startSource;
1324
- }
1325
- if (metadataMap[key].schedule?.scheduleTypeId) {
1326
- metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId;
1327
- delete metadataMap[key].schedule.scheduleTypeId;
1328
- }
1329
- return { key, response };
1330
- }
1331
-
1332
1244
  /**
1333
1245
  * generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve
1334
1246
  *
@@ -1369,18 +1281,9 @@ class Automation extends MetadataType {
1369
1281
  * based on combination of ical string and start/end dates.
1370
1282
  *
1371
1283
  * @param {AutomationSchedule} scheduleObject child of automation metadata used for scheduling
1372
- * @returns {AutomationScheduleSoap} Schedulable object for soap API (currently not rest supported)
1284
+ * @returns {void} throws and error in case of problems
1373
1285
  */
1374
- static _buildSchedule(scheduleObject) {
1375
- /**
1376
- * @type {AutomationScheduleSoap}
1377
- */
1378
- const schedule = {
1379
- Recurrence: {},
1380
- TimeZone: { ID: null, IDSpecified: true },
1381
- RecurrenceRangeType: null,
1382
- StartDateTime: null,
1383
- };
1286
+ static _checkSchedule(scheduleObject) {
1384
1287
  // build recurrence
1385
1288
  const recurHelper = {};
1386
1289
  // ical values are split by ; then have key values split by =
@@ -1391,34 +1294,7 @@ class Automation extends MetadataType {
1391
1294
  if (recurHelper.INTERVAL) {
1392
1295
  recurHelper.INTERVAL = Number.parseInt(recurHelper.INTERVAL);
1393
1296
  }
1394
- // the ical schedule is all in caps but soap objects require Title Case.
1395
- const keyStem = recurHelper.FREQ.charAt(0) + recurHelper.FREQ.slice(1, -2).toLowerCase();
1396
-
1397
- const patternType = recurHelper['BYMONTH']
1398
- ? 'ByMonth'
1399
- : recurHelper['BYWEEK']
1400
- ? 'ByWeek'
1401
- : recurHelper['BYDAY']
1402
- ? 'ByDay'
1403
- : 'Interval';
1404
- schedule.Recurrence[keyStem + 'lyRecurrencePatternType'] = patternType;
1405
- schedule.Recurrence['@_xsi:type'] = keyStem + 'lyRecurrence';
1406
- schedule.RecurrenceType = keyStem + 'ly';
1407
- if (keyStem === 'Dai') {
1408
- schedule.Recurrence['DayInterval'] = recurHelper.INTERVAL;
1409
- } else {
1410
- schedule.Recurrence[keyStem + 'Interval'] = recurHelper.INTERVAL;
1411
- }
1412
- schedule._interval = recurHelper.INTERVAL; // for CLI output only
1413
1297
 
1414
- if (!['Minute', 'Hour', 'Dai'].includes(keyStem)) {
1415
- // todo: add support for weekly
1416
- // todo: add support for monthly
1417
- // todo: add support for yearly
1418
- throw new Error(
1419
- 'Scheduling automatically not supported for Weekly, Monthly and Yearly, please configure manually.'
1420
- );
1421
- }
1422
1298
  if (recurHelper.FREQ === 'MINUTELY' && recurHelper.INTERVAL && recurHelper.INTERVAL < 5) {
1423
1299
  throw new Error(
1424
1300
  'The smallest interval you can configure is 5 minutes. Please adjust your schedule.'
@@ -1433,79 +1309,6 @@ class Automation extends MetadataType {
1433
1309
  `Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
1434
1310
  );
1435
1311
  }
1436
- schedule.TimeZone.ID = scheduleObject.timezoneId;
1437
- schedule._timezoneString = this.definition.timeZoneDifference[scheduleObject.timezoneId];
1438
-
1439
- // add tz to input date to ensure Date() creates a date object with the right tz
1440
- const inputStartDateString = scheduleObject.startDate + schedule._timezoneString;
1441
-
1442
- /** @type {Date | string} */
1443
- let startDateTime;
1444
- if (new Date(inputStartDateString) > new Date()) {
1445
- // if start date is in future take this
1446
- startDateTime = scheduleObject.startDate;
1447
- schedule._StartDateTime = scheduleObject.startDate; // store copy for CLI output
1448
- } else {
1449
- // if start date is in past calculate new start date
1450
- const scheduledDate = new Date(inputStartDateString);
1451
- const futureDate = new Date();
1452
-
1453
- switch (keyStem) {
1454
- case 'Dai': {
1455
- // keep time from template and start today if possible
1456
- if (scheduledDate.getHours() <= futureDate.getHours()) {
1457
- // hour on template has already passed today, start tomorrow
1458
- futureDate.setDate(futureDate.getDate() + 1);
1459
- }
1460
- futureDate.setHours(scheduledDate.getHours());
1461
- futureDate.setMinutes(scheduledDate.getMinutes());
1462
-
1463
- break;
1464
- }
1465
- case 'Hour': {
1466
- // keep minute and start next possible hour
1467
- if (scheduledDate.getMinutes() <= futureDate.getMinutes()) {
1468
- futureDate.setHours(futureDate.getHours() + 1);
1469
- }
1470
- futureDate.setMinutes(scheduledDate.getMinutes());
1471
-
1472
- break;
1473
- }
1474
- case 'Minute': {
1475
- // schedule in next 15 minutes randomly to avoid that all automations run at exactly
1476
- // earliest start 1 minute from now
1477
- // the same time which would slow performance
1478
- futureDate.setMinutes(
1479
- futureDate.getMinutes() + 1 + Math.ceil(Math.random() * 15)
1480
- );
1481
-
1482
- break;
1483
- }
1484
- // No default
1485
- }
1486
- // return time as Dateobject
1487
- startDateTime = futureDate;
1488
- const localTimezoneOffset = futureDate.getTimezoneOffset() / -60;
1489
- schedule._StartDateTime = this._calcTime(localTimezoneOffset, futureDate); // store copy for CLI output
1490
- }
1491
-
1492
- // The Create/Update API expects dates to be in US-Central time
1493
- // The retrieve API returns the date in whatever timezone one chose, hence we need to convert this upon upsert
1494
- schedule.StartDateTime = this._calcTime(
1495
- this.properties.options.serverTimeOffset,
1496
- startDateTime,
1497
- schedule._timezoneString
1498
- );
1499
-
1500
- if (recurHelper.UNTIL) {
1501
- schedule.RecurrenceRangeType = 'EndOn';
1502
- schedule.EndDateTime = scheduleObject.endDate;
1503
- } else {
1504
- schedule.RecurrenceRangeType = 'EndAfter';
1505
- schedule.Occurrences = recurHelper.COUNT;
1506
- }
1507
-
1508
- return schedule;
1509
1312
  }
1510
1313
 
1511
1314
  /**
@@ -1799,7 +1602,7 @@ class Automation extends MetadataType {
1799
1602
  // the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
1800
1603
  const objectId = key ? await this.#getObjectIdForSingleRetrieve(key) : null;
1801
1604
  if (!objectId) {
1802
- Util.logger.error(` - automation ${key} not found`);
1605
+ await this.deleteNotFound(key);
1803
1606
  return false;
1804
1607
  }
1805
1608
  return super.deleteByKeySOAP(key, 'CustomerKey');