mcdev 5.2.0 → 5.3.0

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 (106) hide show
  1. package/.fork/custom-commands.json +12 -0
  2. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  3. package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +19 -0
  4. package/docs/dist/documentation.md +400 -9
  5. package/lib/MetadataTypeDefinitions.js +1 -0
  6. package/lib/MetadataTypeInfo.js +1 -0
  7. package/lib/cli.js +6 -1
  8. package/lib/index.js +4 -1
  9. package/lib/metadataTypes/AttributeSet.js +118 -11
  10. package/lib/metadataTypes/Automation.js +99 -70
  11. package/lib/metadataTypes/DataExtension.js +463 -66
  12. package/lib/metadataTypes/DataExtensionField.js +30 -13
  13. package/lib/metadataTypes/Journey.js +8 -1
  14. package/lib/metadataTypes/MetadataType.js +63 -5
  15. package/lib/metadataTypes/MobileKeyword.js +1 -1
  16. package/lib/metadataTypes/TransactionalEmail.js +94 -17
  17. package/lib/metadataTypes/TransactionalMessage.js +3 -2
  18. package/lib/metadataTypes/Verification.js +230 -0
  19. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +2 -2
  20. package/lib/metadataTypes/definitions/AttributeSet.definition.js +74 -21
  21. package/lib/metadataTypes/definitions/Automation.definition.js +1 -0
  22. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +19 -1
  23. package/lib/metadataTypes/definitions/Verification.definition.js +88 -0
  24. package/package.json +6 -6
  25. package/test/mockRoot/.mcdevrc.json +1 -1
  26. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testExisting_dataExtensionShared.dataExtension-meta.json +59 -0
  27. package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testNew_dataExtensionShared.dataExtension-meta.json +23 -0
  28. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +4 -0
  29. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testExisting_dataExtension.dataExtension-meta.json +1 -0
  30. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +3 -4
  31. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -6
  32. package/test/mockRoot/deploy/testInstance/testBU/verification/testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  33. package/test/mockRoot/deploy/testInstance/testBU/verification/testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
  34. package/test/resources/1111111/data/v1/customobjectdata/key/testExisting_dataExtensionShared/rowset/get-response.json +13 -0
  35. package/test/resources/1111111/dataExtension/create-expected.json +23 -0
  36. package/test/resources/1111111/dataExtension/create-response.xml +59 -0
  37. package/test/resources/1111111/dataExtension/retrieve-expected.json +55 -0
  38. package/test/resources/1111111/dataExtension/retrieve-expected.md +18 -0
  39. package/test/resources/1111111/dataExtension/retrieve-response.xml +27 -1
  40. package/test/resources/1111111/dataExtension/update-expected.json +55 -0
  41. package/test/resources/1111111/dataExtension/update-response.xml +57 -0
  42. package/test/resources/1111111/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtensionShared].[TriggerUpdate_randomNumber_]-response.xml +45 -0
  43. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  44. package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionSharedORDataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
  45. package/test/resources/1111111/dataExtensionField/retrieve-response.xml +98 -0
  46. package/test/resources/1111111/dataExtensionTemplate/retrieve-response.xml +303 -0
  47. package/test/resources/1111111/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +387 -0
  48. package/test/resources/1111111/dataFolder/retrieve-response.xml +353 -9
  49. package/test/resources/9999999/attributeSet/retrieve-expected.json +89 -694
  50. package/test/resources/9999999/automation/build-expected.json +4 -0
  51. package/test/resources/9999999/automation/create-expected.json +4 -0
  52. package/test/resources/9999999/automation/create-testNew_automation-expected.md +1 -0
  53. package/test/resources/9999999/automation/retrieve-expected.json +4 -0
  54. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +1 -0
  55. package/test/resources/9999999/automation/template-expected.json +4 -0
  56. package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +7 -0
  57. package/test/resources/9999999/automation/v1/automations/post-response.json +7 -0
  58. package/test/resources/9999999/automation/v1/dataverifications/post-response.json +12 -0
  59. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/delete-response.json +0 -0
  60. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/get-response.json +12 -0
  61. package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/patch-response.json +12 -0
  62. package/test/resources/9999999/dataExtension/build-expected.json +16 -0
  63. package/test/resources/9999999/dataExtension/delete-response.xml +42 -0
  64. package/test/resources/9999999/dataExtension/retrieve-expected.json +16 -0
  65. package/test/resources/9999999/dataExtension/retrieve-expected.md +3 -1
  66. package/test/resources/9999999/dataExtension/template-expected.json +16 -0
  67. package/test/resources/9999999/dataExtension/update-expected.json +17 -1
  68. package/test/resources/9999999/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtension].[LastName]-response.xml +44 -0
  69. package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +36 -1
  70. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +36 -1
  71. package/test/resources/9999999/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +117 -0
  72. package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +43 -0
  73. package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +387 -0
  74. package/test/resources/9999999/interaction/v1/interactions/233d4413-922c-4568-85a5-e5cc77efc3be/delete-response.json +1 -0
  75. package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +1 -1
  76. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  77. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/delete-response.json +6 -0
  78. package/test/resources/9999999/transactionalEmail/build-expected.json +3 -7
  79. package/test/resources/9999999/transactionalEmail/get-expected.json +3 -7
  80. package/test/resources/9999999/transactionalEmail/patch-expected.json +3 -7
  81. package/test/resources/9999999/transactionalEmail/post-expected.json +3 -7
  82. package/test/resources/9999999/transactionalEmail/template-expected.json +3 -7
  83. package/test/resources/9999999/verification/build-expected.json +11 -0
  84. package/test/resources/9999999/verification/get-expected.json +11 -0
  85. package/test/resources/9999999/verification/patch-expected.json +11 -0
  86. package/test/resources/9999999/verification/post-expected.json +11 -0
  87. package/test/resources/9999999/verification/template-expected.json +11 -0
  88. package/test/type.attributeGroup.test.js +4 -4
  89. package/test/type.attributeSet.test.js +5 -5
  90. package/test/type.automation.test.js +29 -23
  91. package/test/type.dataExtension.test.js +205 -45
  92. package/test/type.dataExtract.test.js +10 -3
  93. package/test/type.fileTransfer.test.js +10 -3
  94. package/test/type.importFile.test.js +10 -3
  95. package/test/type.journey.test.js +38 -11
  96. package/test/type.mobileKeyword.test.js +6 -4
  97. package/test/type.mobileMessage.test.js +6 -4
  98. package/test/type.query.test.js +8 -6
  99. package/test/type.script.test.js +6 -1
  100. package/test/type.transactionalEmail.test.js +12 -11
  101. package/test/type.transactionalPush.test.js +2 -4
  102. package/test/type.transactionalSMS.test.js +2 -4
  103. package/test/type.triggeredSend.test.js +6 -4
  104. package/test/type.verification.test.js +173 -0
  105. package/test/utils.js +7 -1
  106. package/types/mcdev.d.js +14 -0
@@ -191,14 +191,21 @@ class Journey extends MetadataType {
191
191
  );
192
192
  const results = this.parseResponseBody(response, key);
193
193
  singleKey = results[key].id;
194
+ if (version && version > results[key].version) {
195
+ Util.logger.error(
196
+ `The chosen version (${version}) is higher than the latest known version (${results[key].version}). Please choose a lower version.`
197
+ );
198
+ return false;
199
+ }
194
200
  Util.logger.debug(`Deleting interaction ${key} via its ID ${singleKey}`);
195
201
  }
196
202
  if (!/^\d+$/.test(version)) {
197
- throw new TypeError(
203
+ Util.logger.error(
198
204
  '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: ' +
199
205
  key +
200
206
  '/4'
201
207
  );
208
+ return false;
202
209
  }
203
210
  Util.logger.warn(
204
211
  `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.`
@@ -139,9 +139,10 @@ class MetadataType {
139
139
  *
140
140
  * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
141
141
  * @param {object} apiResponse varies depending on the API call
142
+ * @param {TYPE.MetadataTypeItem} metadataEntryWithAllFields like metadataEntry but before non-creatable fields were stripped
142
143
  * @returns {void}
143
144
  */
144
- static postCreateTasks(metadataEntry, apiResponse) {}
145
+ static postCreateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {}
145
146
 
146
147
  /**
147
148
  * helper for {@link MetadataType.updateREST}
@@ -265,11 +266,10 @@ class MetadataType {
265
266
  *
266
267
  * @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
267
268
  * @param {string[]} [subTypeArr] optionally limit to a single subtype
268
- * @param {string} [key] customer key of single item to retrieve
269
269
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} metadata
270
270
  */
271
- static async retrieveForCache(additionalFields, subTypeArr, key) {
272
- return this.retrieve(null, additionalFields, subTypeArr, key);
271
+ static async retrieveForCache(additionalFields, subTypeArr) {
272
+ return this.retrieve(null, additionalFields, subTypeArr);
273
273
  }
274
274
  /**
275
275
  * Gets metadata cache with limited fields and does not store value to disk
@@ -808,11 +808,12 @@ class MetadataType {
808
808
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
809
809
  */
810
810
  static async createREST(metadataEntry, uri) {
811
+ const metadataClone = JSON.parse(JSON.stringify(metadataEntry));
811
812
  this.removeNotCreateableFields(metadataEntry);
812
813
  try {
813
814
  // set to empty object in case API returned nothing to be able to update it in helper classes
814
815
  const response = (await this.client.rest.post(uri, metadataEntry)) || {};
815
- await this.postCreateTasks(metadataEntry, response);
816
+ await this.postCreateTasks(metadataEntry, response, metadataClone);
816
817
  Util.logger.info(
817
818
  ` - created ${this.definition.type}: ${
818
819
  metadataEntry[this.definition.keyField] ||
@@ -1060,6 +1061,7 @@ class MetadataType {
1060
1061
  const results = this.parseResponseBody(response, singleRetrieve);
1061
1062
  // get extended metadata if applicable
1062
1063
  if (this.definition.hasExtended) {
1064
+ Util.logger.debug(' - retrieving extended metadata');
1063
1065
  const extended = await this.client.rest.getCollection(
1064
1066
  Object.keys(results).map((key) => uri + results[key][this.definition.idField])
1065
1067
  );
@@ -1088,6 +1090,62 @@ class MetadataType {
1088
1090
  type: this.definition.type,
1089
1091
  };
1090
1092
  }
1093
+ /**
1094
+ *
1095
+ * @param {object[]} urlArray {uri: string, id: string} combo of URL and ID/key of metadata
1096
+ * @param {number} [concurrentRequests] optionally set a different amount of concurrent requests
1097
+ * @param {boolean} [logAmountOfUrls] if true, prints an info message about to-be loaded amount of metadata
1098
+ * @returns {Promise.<{metadata: (TYPE.MetadataTypeMap | TYPE.MetadataTypeItem), type: string}>} Promise of item map (single item for templated result)
1099
+ */
1100
+ static async retrieveRESTcollection(urlArray, concurrentRequests = 10, logAmountOfUrls = true) {
1101
+ if (logAmountOfUrls) {
1102
+ Util.logger.info(
1103
+ Util.getGrayMsg(
1104
+ ` - ${urlArray?.length} ${this.definition.type}${
1105
+ urlArray?.length === 1 ? '' : 's'
1106
+ } found. Retrieving details...`
1107
+ )
1108
+ );
1109
+ }
1110
+ const rateLimit = pLimit(concurrentRequests);
1111
+
1112
+ const metadataArr = urlArray.length
1113
+ ? await Promise.all(
1114
+ urlArray.map(async (item) =>
1115
+ rateLimit(async () => {
1116
+ try {
1117
+ return await this.client.rest.get(item.uri);
1118
+ } catch (ex) {
1119
+ return this.handleRESTErrors(ex, item.id);
1120
+ }
1121
+ })
1122
+ )
1123
+ )
1124
+ : [];
1125
+ const results = {};
1126
+ for (const item of metadataArr) {
1127
+ const key = item[this.definition.keyField];
1128
+ results[key] = item;
1129
+ }
1130
+ return {
1131
+ metadata: results,
1132
+ type: this.definition.type,
1133
+ };
1134
+ }
1135
+
1136
+ /**
1137
+ * helper for {@link this.retrieveRESTcollection}
1138
+ *
1139
+ * @param {Error} ex exception
1140
+ * @param {string} id id or key of item
1141
+ * @returns {null} -
1142
+ */
1143
+ static handleRESTErrors(ex, id) {
1144
+ // if the ID is too short, the system will throw the 400 error
1145
+ Util.logger.debug(` ☇ skipping ${this.definition.type} ${id}: ${ex.message} ${ex.code}`);
1146
+
1147
+ return null;
1148
+ }
1091
1149
  /**
1092
1150
  * Used to execute a query/automation etc.
1093
1151
  *
@@ -501,7 +501,7 @@ class MobileKeyword extends MetadataType {
501
501
  */
502
502
  static async deleteByKey(key) {
503
503
  // get id from cache
504
- const { metadata } = await this.retrieveForCache(key);
504
+ const { metadata } = await this.retrieveForCache(undefined, undefined, key);
505
505
  if (!metadata[key]) {
506
506
  Util.logger.error(`Could not find ${this.definition.type} with key ${key}.`);
507
507
  return false;
@@ -40,21 +40,24 @@ class TransactionalEmail extends TransactionalMessage {
40
40
  */
41
41
  static async preDeployTasks(metadata) {
42
42
  // asset
43
- if (metadata.content?.customerKey) {
43
+ if (metadata.r__asset_customerKey) {
44
44
  // we merely want to be able to show an error if it does not exist
45
- cache.searchForField(
46
- 'asset',
47
- metadata.content.customerKey,
48
- 'customerKey',
49
- 'customerKey'
50
- );
45
+ metadata.content = {
46
+ customerKey: cache.searchForField(
47
+ 'asset',
48
+ metadata.r__asset_customerKey,
49
+ 'customerKey',
50
+ 'customerKey'
51
+ ),
52
+ };
53
+ delete metadata.r__asset_customerKey;
51
54
  }
52
55
  // subscriptions: dataExtension
53
- if (metadata.subscriptions?.dataExtension) {
56
+ if (metadata.subscriptions?.r__dataExtension_CustomerKey) {
54
57
  // we merely want to be able to show an error if it does not exist
55
- cache.searchForField(
58
+ metadata.subscriptions.dataExtension = cache.searchForField(
56
59
  'dataExtension',
57
- metadata.subscriptions.dataExtension,
60
+ metadata.subscriptions.r__dataExtension_CustomerKey,
58
61
  'CustomerKey',
59
62
  'CustomerKey'
60
63
  );
@@ -69,15 +72,49 @@ class TransactionalEmail extends TransactionalMessage {
69
72
  }
70
73
 
71
74
  // journey
72
- if (metadata.journey?.interactionKey) {
73
- // ! update & create enpoints dont accept journey.interactionKey. They only allow to create a new journey
74
- metadata.options ||= {};
75
- metadata.options.createJourney = true; // only send this during create or else we might end up with an unexpected outcome
76
- delete metadata.journey.interactionKey;
77
- }
75
+ // ! update & create enpoints dont accept journey.interactionKey. They only allow to create a new journey
76
+ metadata.options ||= {};
77
+ metadata.options.createJourney = true; // only send this during create or else we might end up with an unexpected outcome
78
78
 
79
79
  return metadata;
80
80
  }
81
+ /**
82
+ * helper for {@link TransactionalEmail.createREST}
83
+ *
84
+ * @param {TYPE.MetadataTypeItem} _ not used
85
+ * @param {object} apiResponse varies depending on the API call
86
+ * @returns {void}
87
+ */
88
+ static async postCreateTasks(_, apiResponse) {
89
+ if (apiResponse.journey?.interactionKey) {
90
+ Util.logger.warn(
91
+ ` - created journey: ${apiResponse.journey.interactionKey} (auto-created when ${this.definition.type} ${apiResponse.definitionKey} was created)`
92
+ );
93
+ // when we create new transactionalEmails, we should also download the new journey that was created with it
94
+ this._createdJourneyKeys ||= [];
95
+ this._createdJourneyKeys.push(apiResponse.journey?.interactionKey);
96
+
97
+ // do what postRetrieveTasks won't be able to do without spending lots of time on caching
98
+ apiResponse.r__journey_key = apiResponse.journey.interactionKey;
99
+ delete apiResponse.journey;
100
+ }
101
+ }
102
+ /**
103
+ * Gets executed after deployment of metadata type
104
+ *
105
+ * @returns {void}
106
+ */
107
+ static postDeployTasks() {
108
+ if (this._createdJourneyKeys?.length) {
109
+ Util.logger.warn(
110
+ `Please download related journeys via: mcdev r ${this.buObject.credential}/${
111
+ this.buObject.businessUnit
112
+ } journey "${this._createdJourneyKeys.join(',')}"`
113
+ );
114
+ }
115
+ delete this._createdJourneyKeys;
116
+ }
117
+
81
118
  /**
82
119
  * manages post retrieve steps
83
120
  *
@@ -87,6 +124,7 @@ class TransactionalEmail extends TransactionalMessage {
87
124
  static postRetrieveTasks(metadata) {
88
125
  // asset
89
126
  if (metadata.content?.customerKey) {
127
+ metadata.r__asset_customerKey = metadata.content.customerKey;
90
128
  try {
91
129
  // we merely want to be able to show an error if it does not exist
92
130
  cache.searchForField(
@@ -102,9 +140,13 @@ class TransactionalEmail extends TransactionalMessage {
102
140
  }): ${ex.message}.`
103
141
  );
104
142
  }
143
+ delete metadata.content;
105
144
  }
145
+
106
146
  // subscriptions: dataExtension
107
147
  if (metadata.subscriptions?.dataExtension) {
148
+ metadata.subscriptions.r__dataExtension_CustomerKey =
149
+ metadata.subscriptions.dataExtension;
108
150
  try {
109
151
  // we merely want to be able to show a warning if it does not exist
110
152
  cache.searchForField(
@@ -120,6 +162,7 @@ class TransactionalEmail extends TransactionalMessage {
120
162
  }): ${ex.message}.`
121
163
  );
122
164
  }
165
+ delete metadata.subscriptions.dataExtension;
123
166
  }
124
167
  // subscriptions: list
125
168
  if (metadata.subscriptions?.list) {
@@ -140,9 +183,15 @@ class TransactionalEmail extends TransactionalMessage {
140
183
  }
141
184
  // journey
142
185
  if (metadata.journey?.interactionKey) {
186
+ metadata.r__journey_key = metadata.journey.interactionKey;
143
187
  try {
144
188
  // we merely want to be able to show a warning if it does not exist
145
- cache.searchForField('journey', metadata.journey.interactionKey, 'key', 'key');
189
+ metadata.r__journey_key = cache.searchForField(
190
+ 'journey',
191
+ metadata.journey.interactionKey,
192
+ 'key',
193
+ 'key'
194
+ );
146
195
  } catch (ex) {
147
196
  Util.logger.warn(
148
197
  ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
@@ -150,10 +199,38 @@ class TransactionalEmail extends TransactionalMessage {
150
199
  }): ${ex.message}.`
151
200
  );
152
201
  }
202
+ delete metadata.journey;
153
203
  }
154
204
 
155
205
  return metadata;
156
206
  }
207
+ /**
208
+ * Delete a metadata item from the specified business unit
209
+ *
210
+ * @param {string} key Identifier of item
211
+ * @returns {Promise.<boolean>} deletion success status
212
+ */
213
+ static async deleteByKey(key) {
214
+ const metadataMapObj = await this.retrieveForCache(key);
215
+ const journeyKey = metadataMapObj?.metadata?.[key]?.journey?.interactionKey;
216
+
217
+ const isDeleted = await super.deleteByKeyREST(
218
+ '/messaging/v1/' + this.subType + '/definitions/' + key,
219
+ key,
220
+ false
221
+ );
222
+ if (isDeleted && journeyKey) {
223
+ const Journey = require('./Journey');
224
+ Util.logger.info(
225
+ ` - deleted ${Journey.definition.type}: ${journeyKey} (SFMC auto-deletes the related journey of ${this.definition.type} ${key})`
226
+ );
227
+ Journey.buObject = this.buObject;
228
+ Journey.properties = this.properties;
229
+ Journey.client = this.client;
230
+ Journey.postDeleteTasks(journeyKey);
231
+ }
232
+ return isDeleted;
233
+ }
157
234
  }
158
235
 
159
236
  // Assign definition to static attributes
@@ -67,13 +67,14 @@ class TransactionalMessage extends MetadataType {
67
67
  /**
68
68
  * Retrieves event definition metadata for caching
69
69
  *
70
+ * @param {string} [key] customer key of single item to cache
70
71
  * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
71
72
  */
72
- static retrieveForCache() {
73
+ static retrieveForCache(key) {
73
74
  // the call to /messaging/v1/email/definitions/ does not return definitionId
74
75
  // definitionId is required for resolving dependencies on interactions.
75
76
  // we should therefore use the already defined retrieve method
76
- return this.retrieve();
77
+ return this.retrieve(undefined, undefined, undefined, key);
77
78
  }
78
79
  /**
79
80
  * Updates a single item
@@ -0,0 +1,230 @@
1
+ 'use strict';
2
+
3
+ const TYPE = require('../../types/mcdev.d');
4
+ const MetadataType = require('./MetadataType');
5
+ const Automation = require('./Automation');
6
+ const Util = require('../util/util');
7
+ const cache = require('../util/cache');
8
+
9
+ /**
10
+ * Verification MetadataType
11
+ *
12
+ * @augments MetadataType
13
+ */
14
+ class Verification extends MetadataType {
15
+ /**
16
+ * Retrieves Metadata of Data Verification Activity.
17
+ *
18
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
19
+ * @param {void} [_] unused parameter
20
+ * @param {void} [__] unused parameter
21
+ * @param {string} key customer key of single item to retrieve
22
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
23
+ */
24
+ static async retrieve(retrieveDir, _, __, key) {
25
+ let paramArr = [];
26
+ if (key?.startsWith('id:')) {
27
+ paramArr = [key.slice(3)];
28
+ } else if (key) {
29
+ paramArr = [key];
30
+ }
31
+ if (!paramArr.length) {
32
+ // there is no API endpoint to retrieve all dataVerification items, so we need to retrieve all automations and iterate over their activities
33
+ Util.logger.info(` - Caching dependent Metadata: automation`);
34
+ Automation.client = this.client;
35
+ Automation.buObject = this.buObject;
36
+ Automation.properties = this.properties;
37
+ Automation._skipNotificationRetrieve = true;
38
+ delete Automation._cachedMetadataMap;
39
+ const automationsMapObj = await Automation.retrieve();
40
+ delete Automation._skipNotificationRetrieve;
41
+ if (automationsMapObj?.metadata && Object.keys(automationsMapObj?.metadata).length) {
42
+ if (!key) {
43
+ // if we are not retrieving a single item, cache the automations for later use during retrieval of automations
44
+ Automation._cachedMetadataMap = automationsMapObj?.metadata;
45
+ }
46
+ // automations found, lets iterate over their activities to find the dataVerification items
47
+ const dataVerificationIds = [];
48
+ for (const automation of Object.values(automationsMapObj.metadata)) {
49
+ if (automation.steps) {
50
+ for (const step of automation.steps) {
51
+ for (const activity of step.activities) {
52
+ if (
53
+ activity.objectTypeId === 1000 &&
54
+ activity.activityObjectId &&
55
+ activity.activityObjectId !==
56
+ '00000000-0000-0000-0000-000000000000'
57
+ ) {
58
+ dataVerificationIds.push(activity.activityObjectId);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ if (dataVerificationIds.length) {
65
+ paramArr.push(...dataVerificationIds);
66
+ }
67
+ }
68
+ }
69
+ const results = {};
70
+ if (paramArr.length) {
71
+ const response = await this.retrieveRESTcollection(
72
+ paramArr.map((id) => ({ id, uri: '/automation/v1/dataverifications/' + id })),
73
+ undefined,
74
+ !key
75
+ );
76
+ if (response?.metadata) {
77
+ Object.assign(results, response.metadata);
78
+ }
79
+ }
80
+ if (retrieveDir) {
81
+ const savedMetadata = await this.saveResults(results, retrieveDir, null, null);
82
+ Util.logger.info(
83
+ `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` +
84
+ Util.getKeysString(key)
85
+ );
86
+ }
87
+
88
+ return {
89
+ metadata: results,
90
+ type: this.definition.type,
91
+ };
92
+ }
93
+ /**
94
+ * helper for {@link this.retrieveRESTcollection}
95
+ *
96
+ * @param {Error} ex exception
97
+ * @param {string} id id or key of item
98
+ * @returns {null} -
99
+ */
100
+ static handleRESTErrors(ex, id) {
101
+ if (ex.message === 'Not Found' || ex.message === 'Request failed with status code 400') {
102
+ // if the ID is too short, the system will throw the 400 error
103
+ Util.logger.debug(
104
+ ` ☇ skipping ${this.definition.type} ${id}: ${ex.message} ${ex.code}`
105
+ );
106
+ } else {
107
+ // if we do get here, we should log the error and continue instead of failing to download all automations
108
+ Util.logger.error(
109
+ ` ☇ skipping ${this.definition.type} ${id}: ${ex.message} ${ex.code}`
110
+ );
111
+ }
112
+ return null;
113
+ }
114
+
115
+ /**
116
+ * Retrieves Metadata of Data Extract Activity for caching
117
+ *
118
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise of metadata
119
+ */
120
+ static async retrieveForCache() {
121
+ return this.retrieve();
122
+ }
123
+
124
+ /**
125
+ * Creates a single Data Extract
126
+ *
127
+ * @param {TYPE.VerificationItem} metadata a single Data Extract
128
+ * @returns {Promise} Promise
129
+ */
130
+ static create(metadata) {
131
+ return super.createREST(metadata, '/automation/v1/dataverifications/');
132
+ }
133
+
134
+ /**
135
+ * helper for {@link MetadataType.createREST}
136
+ *
137
+ * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
138
+ * @param {object} apiResponse varies depending on the API call
139
+ * @param {TYPE.MetadataTypeItem} metadataEntryWithAllFields like metadataEntry but before non-creatable fields were stripped
140
+ * @returns {void}
141
+ */
142
+ static async postCreateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {
143
+ if (!apiResponse?.[this.definition.idField]) {
144
+ return;
145
+ }
146
+ Util.logger.warn(
147
+ ` - ${this.definition.type} ${
148
+ metadataEntryWithAllFields?.[this.definition.idField]
149
+ }: new key ${
150
+ apiResponse?.[this.definition.idField]
151
+ } automatically assigned during creation`
152
+ );
153
+ metadataEntry[this.definition.idField] = apiResponse?.[this.definition.idField];
154
+
155
+ // map structure: cred/bu --> type --> old key --> new key
156
+ const buName = this.buObject.credential + '/' + this.buObject.businessUnit;
157
+ Automation.createdKeyMap ||= {};
158
+ Automation.createdKeyMap[buName] ||= {};
159
+ Automation.createdKeyMap[buName][this.definition.type] ||= {};
160
+ Automation.createdKeyMap[buName][this.definition.type][
161
+ metadataEntryWithAllFields[this.definition.idField]
162
+ ] = metadataEntry[this.definition.idField];
163
+ }
164
+
165
+ /**
166
+ * Updates a single Data Extract
167
+ *
168
+ * @param {TYPE.VerificationItem} metadata a single Data Extract
169
+ * @returns {Promise} Promise
170
+ */
171
+ static update(metadata) {
172
+ return super.updateREST(
173
+ metadata,
174
+ '/automation/v1/dataverifications/' + metadata.dataVerificationDefinitionId
175
+ );
176
+ }
177
+
178
+ /**
179
+ * prepares a verification for deployment
180
+ *
181
+ * @param {TYPE.VerificationItem} metadata a single verification activity definition
182
+ * @returns {TYPE.VerificationItem} metadata object
183
+ */
184
+ static preDeployTasks(metadata) {
185
+ metadata.targetObjectId = cache.searchForField(
186
+ 'dataExtension',
187
+ metadata.r__dataExtension_CustomerKey,
188
+ 'CustomerKey',
189
+ 'ObjectID'
190
+ );
191
+ delete metadata.r__dataExtension_CustomerKey;
192
+ return metadata;
193
+ }
194
+ /**
195
+ * parses retrieved Metadata before saving
196
+ *
197
+ * @param {TYPE.VerificationItem} metadata a single verification activity definition
198
+ * @returns {TYPE.VerificationItem} Array with one metadata object and one sql string
199
+ */
200
+ static postRetrieveTasks(metadata) {
201
+ try {
202
+ metadata.r__dataExtension_CustomerKey = cache.searchForField(
203
+ 'dataExtension',
204
+ metadata.targetObjectId,
205
+ 'ObjectID',
206
+ 'CustomerKey'
207
+ );
208
+ delete metadata.targetObjectId;
209
+ } catch (ex) {
210
+ Util.logger.warn(
211
+ ` - ${this.definition.type} ${metadata[this.definition.keyField]}: ${ex.message}`
212
+ );
213
+ }
214
+ return metadata;
215
+ }
216
+ /**
217
+ * Delete a metadata item from the specified business unit
218
+ *
219
+ * @param {string} key Identifier of item
220
+ * @returns {Promise.<boolean>} deletion success status
221
+ */
222
+ static deleteByKey(key) {
223
+ return super.deleteByKeyREST('/automation/v1/dataverifications/' + key, key);
224
+ }
225
+ }
226
+
227
+ // Assign definition to static attributes
228
+ Verification.definition = require('../MetadataTypeDefinitions').verification;
229
+
230
+ module.exports = Verification;
@@ -8,8 +8,8 @@ module.exports = {
8
8
  nameField: 'definitionName.value',
9
9
  restPagination: false, // Hub API does not support pagination and returns everything instead
10
10
  type: 'attributeGroup',
11
- typeDescription: 'BETA: Groupings of Set Definitions (Data Extensions) in Data Designer.',
12
- typeRetrieveByDefault: false,
11
+ typeDescription: 'Groupings of Attribute Sets (Data Extensions) in Data Designer.',
12
+ typeRetrieveByDefault: true,
13
13
  typeName: 'Data Designer Attribute Groups',
14
14
  fields: {
15
15
  applicationID: {