mcdev 7.8.0 → 7.10.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 (63) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/@types/lib/index.d.ts +2 -1
  3. package/@types/lib/index.d.ts.map +1 -1
  4. package/@types/lib/metadataTypes/Asset.d.ts +8 -0
  5. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  6. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/DomainVerification.d.ts +10 -2
  8. package/@types/lib/metadataTypes/DomainVerification.d.ts.map +1 -1
  9. package/@types/lib/metadataTypes/MetadataType.d.ts +8 -1
  10. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/MobileKeyword.d.ts +8 -6
  12. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/MobileMessage.d.ts +8 -0
  14. package/@types/lib/metadataTypes/MobileMessage.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/Verification.d.ts +12 -5
  16. package/@types/lib/metadataTypes/Verification.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/definitions/Verification.definition.d.ts +10 -0
  18. package/@types/lib/util/cli.d.ts +0 -9
  19. package/@types/lib/util/cli.d.ts.map +1 -1
  20. package/@types/lib/util/init.config.d.ts +2 -2
  21. package/@types/lib/util/init.config.d.ts.map +1 -1
  22. package/@types/lib/util/util.d.ts +15 -2
  23. package/@types/lib/util/util.d.ts.map +1 -1
  24. package/@types/types/mcdev.d.d.ts +20 -0
  25. package/@types/types/mcdev.d.d.ts.map +1 -1
  26. package/boilerplate/files/eslint.config.js +30 -27
  27. package/boilerplate/forcedUpdates.json +4 -0
  28. package/lib/index.js +28 -5
  29. package/lib/metadataTypes/Automation.js +0 -18
  30. package/lib/metadataTypes/DomainVerification.js +62 -2
  31. package/lib/metadataTypes/MetadataType.js +18 -4
  32. package/lib/metadataTypes/MobileKeyword.js +2 -52
  33. package/lib/metadataTypes/Verification.js +94 -58
  34. package/lib/metadataTypes/definitions/TriggeredSend.definition.js +3 -3
  35. package/lib/metadataTypes/definitions/Verification.definition.js +8 -2
  36. package/lib/util/cli.js +1 -30
  37. package/lib/util/config.js +3 -3
  38. package/lib/util/init.config.js +89 -28
  39. package/lib/util/util.js +84 -2
  40. package/package.json +1 -1
  41. package/test/general.test.js +3 -18
  42. package/test/mockRoot/.mcdevrc.json +2 -4
  43. package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +1 -1
  44. package/test/mockRoot/deploy/testInstance/testBU/verification/{testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json → testExisting_automation__s1.7.verification-meta.json} +1 -1
  45. package/test/mockRoot/deploy/testInstance/testBU/verification/{testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json → testNew_automation__s1.7.verification-meta.json} +1 -1
  46. package/test/resources/9999999/automation/build-expected.json +1 -1
  47. package/test/resources/9999999/automation/clone-expected.json +1 -1
  48. package/test/resources/9999999/automation/create-callout-expected.json +1 -1
  49. package/test/resources/9999999/automation/create-expected.json +1 -1
  50. package/test/resources/9999999/automation/create-testNew_automation-expected.md +1 -1
  51. package/test/resources/9999999/automation/retrieve-expected.json +1 -1
  52. package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +1 -1
  53. package/test/resources/9999999/automation/template-expected.json +1 -1
  54. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-sha,automatio,dataexten,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml +291 -0
  55. package/test/resources/9999999/verification/build-expected.json +1 -1
  56. package/test/resources/9999999/verification/get-expected.json +1 -1
  57. package/test/resources/9999999/verification/patch-expected.json +1 -1
  58. package/test/resources/9999999/verification/post-expected.json +1 -1
  59. package/test/resources/9999999/verification/template-expected.json +1 -1
  60. package/test/type.automation.test.js +3 -3
  61. package/test/type.verification.test.js +7 -13
  62. package/test/utils.js +3 -1
  63. package/types/mcdev.d.js +5 -0
@@ -16,6 +16,31 @@ export default [
16
16
  {
17
17
  plugins: { jsdoc },
18
18
  rules: {
19
+ 'unicorn/better-regex': 'off',
20
+ 'unicorn/prefer-string-raw': 'off',
21
+ 'unicorn/catch-error-name': [
22
+ 'error',
23
+ {
24
+ name: 'ex'
25
+ }
26
+ ],
27
+ 'unicorn/explicit-length-check': 'off',
28
+ 'unicorn/no-null': 'off',
29
+ 'unicorn/prefer-module': 'off',
30
+ 'unicorn/prevent-abbreviations': 'off',
31
+ 'unicorn/filename-case': 'off',
32
+ 'unicorn/no-array-callback-reference': 'off',
33
+ 'unicorn/no-array-reduce': 'off',
34
+ 'unicorn/no-await-expression-member': 'off',
35
+ 'unicorn/no-hex-escape': 'off',
36
+ 'unicorn/no-nested-ternary': 'off',
37
+ 'unicorn/no-static-only-class': 'off',
38
+ 'unicorn/no-unused-properties': 'warn',
39
+ 'unicorn/numeric-separators-style': 'off',
40
+ 'unicorn/prefer-array-some': 'off',
41
+ 'unicorn/prefer-set-has': 'off',
42
+ 'unicorn/prefer-spread': 'off',
43
+ 'unicorn/prefer-string-replace-all': 'error',
19
44
  'padded-blocks': 'off',
20
45
  'prefer-rest-params': 'off',
21
46
  'prefer-spread': 'off',
@@ -49,7 +74,11 @@ export default [
49
74
  },
50
75
  {
51
76
  ...sfmcSsjs.configs.recommended,
52
- files: ['**/*.ssjs']
77
+ files: ['**/*.ssjs'],
78
+ rules: {
79
+ 'unicorn/text-encoding-identifier-case': 'off',
80
+ 'unicorn/prefer-string-replace-all': 'off'
81
+ }
53
82
  },
54
83
  {
55
84
  files: ['**/*.js'],
@@ -106,32 +135,6 @@ export default [
106
135
 
107
136
  rules: {
108
137
  'logical-assignment-operators': ['error', 'always'],
109
- 'unicorn/better-regex': 'off',
110
-
111
- 'unicorn/catch-error-name': [
112
- 'error',
113
- {
114
- name: 'ex'
115
- }
116
- ],
117
-
118
- 'unicorn/explicit-length-check': 'off',
119
- 'unicorn/no-null': 'off',
120
- 'unicorn/prefer-module': 'off',
121
- 'unicorn/prevent-abbreviations': 'off',
122
- 'unicorn/filename-case': 'off',
123
- 'unicorn/no-array-callback-reference': 'off',
124
- 'unicorn/no-array-reduce': 'off',
125
- 'unicorn/no-await-expression-member': 'off',
126
- 'unicorn/no-hex-escape': 'off',
127
- 'unicorn/no-nested-ternary': 'off',
128
- 'unicorn/no-static-only-class': 'off',
129
- 'unicorn/no-unused-properties': 'warn',
130
- 'unicorn/numeric-separators-style': 'off',
131
- 'unicorn/prefer-array-some': 'off',
132
- 'unicorn/prefer-set-has': 'off',
133
- 'unicorn/prefer-spread': 'off',
134
- 'unicorn/prefer-string-replace-all': 'error',
135
138
  'arrow-body-style': ['error', 'as-needed'],
136
139
  curly: 'error',
137
140
 
@@ -1,4 +1,8 @@
1
1
  [
2
+ {
3
+ "version": "7.9.1",
4
+ "files": ["eslint.config.js"]
5
+ },
2
6
  {
3
7
  "version": "7.7.2",
4
8
  "files": [".vscode/extensions.json"]
package/lib/index.js CHANGED
@@ -1030,7 +1030,7 @@ class Mcdev {
1030
1030
  );
1031
1031
 
1032
1032
  // try re-retrieve without passing selectedTypes to ensure we find all dependencies
1033
- await this._reRetrieve(businessUnit, true);
1033
+ await this._reRetrieve(businessUnit, true, null, typeKeyList);
1034
1034
 
1035
1035
  Util.logger.info(
1036
1036
  'Searching for selected items and their dependencies in your project folder'
@@ -1313,11 +1313,17 @@ class Mcdev {
1313
1313
  * @param {string} businessUnit references credentials from properties.json
1314
1314
  * @param {boolean} [alwaysAsk] by default this code only runs if --retrieve is set; this flag allows to always ask
1315
1315
  * @param {TypeKeyCombo} [selectedTypes] limit retrieval to given metadata type
1316
+ * @param {TypeKeyCombo} [defaultPlusTheseTypes] if we run build for a non-standard type we need to tell it what to download on top
1316
1317
  * @returns {Promise.<void>} -
1317
1318
  */
1318
- static async _reRetrieve(businessUnit, alwaysAsk = false, selectedTypes) {
1319
+ static async _reRetrieve(
1320
+ businessUnit,
1321
+ alwaysAsk = false,
1322
+ selectedTypes,
1323
+ defaultPlusTheseTypes
1324
+ ) {
1319
1325
  let runRetrieveNow;
1320
- if (!Util.OPTIONS.skipInteraction && !Util.OPTIONS.retrieve && alwaysAsk) {
1326
+ if (!Util.OPTIONS.skipInteraction && Util.OPTIONS.retrieve === undefined && alwaysAsk) {
1321
1327
  runRetrieveNow = await confirm({
1322
1328
  message: `Do you want to re-retrieve ${selectedTypes ? Util.convertTypeKeyToString(selectedTypes) : 'all metadata'} for ${businessUnit} now?`,
1323
1329
  default: false,
@@ -1327,8 +1333,25 @@ class Mcdev {
1327
1333
  Util.logger.info(
1328
1334
  `Re-retrieving ${businessUnit}: ${selectedTypes ? Util.convertTypeKeyToString(selectedTypes) : 'all metadata'}`
1329
1335
  );
1330
- // we need to work with a clone here because retrieve() modifies the object passed in as 2nd parameter
1331
- const retrieveTypes = structuredClone(selectedTypes);
1336
+ /** @type {TypeKeyCombo | string[]} */
1337
+ let retrieveTypes = selectedTypes ? structuredClone(selectedTypes) : null;
1338
+ if (defaultPlusTheseTypes) {
1339
+ if (selectedTypes) {
1340
+ // we need to work with a clone here because retrieve() modifies the object passed in as 2nd parameter
1341
+ retrieveTypes = Object.assign(
1342
+ retrieveTypes,
1343
+ structuredClone(defaultPlusTheseTypes)
1344
+ );
1345
+ } else {
1346
+ const properties = await config.getProperties();
1347
+ retrieveTypes = [
1348
+ ...new Set(
1349
+ properties.metaDataTypes.retrieve.map((type) => type.split('-')[0])
1350
+ ),
1351
+ ];
1352
+ retrieveTypes.push(...Object.keys(defaultPlusTheseTypes));
1353
+ }
1354
+ }
1332
1355
  await this.retrieve(businessUnit, retrieveTypes);
1333
1356
  }
1334
1357
  }
@@ -987,7 +987,6 @@ class Automation extends MetadataType {
987
987
  delete metadata.schedule;
988
988
  delete metadata.type;
989
989
  let i = 0;
990
- const buName = this.buObject.credential + '/' + this.buObject.businessUnit;
991
990
  if (metadata.steps) {
992
991
  for (const step of metadata.steps) {
993
992
  let displayOrder = 0;
@@ -997,23 +996,6 @@ class Automation extends MetadataType {
997
996
  activity.r__key &&
998
997
  this.definition.dependencies.includes(activity.r__type)
999
998
  ) {
1000
- if (
1001
- activity.r__type === 'verification' &&
1002
- this.createdKeyMap?.[buName]?.verification?.[activity.r__key]
1003
- ) {
1004
- Util.logger.debug(
1005
- Util.getGrayMsg(
1006
- ` - updated verification activity name from ${
1007
- activity.r__key
1008
- } to ${
1009
- this.createdKeyMap[buName].verification[activity.r__key]
1010
- }`
1011
- )
1012
- );
1013
- // map structure: cred/bu --> type --> old key --> new key
1014
- activity.r__key =
1015
- this.createdKeyMap[buName].verification[activity.r__key];
1016
- }
1017
999
  // automations can have empty placeholder for activities with only their type defined
1018
1000
  activity.activityObjectId = cache.searchForField(
1019
1001
  activity.r__type,
@@ -54,7 +54,60 @@ class DomainVerification extends MetadataType {
54
54
  * @returns {Promise} Promise
55
55
  */
56
56
  static create(metadataItem) {
57
- return super.createREST(metadataItem, '/messaging/v1/domainverification/');
57
+ return metadataItem.domain === 'bulk' &&
58
+ (Array.isArray(metadataItem.addresses) ||
59
+ (metadataItem.deTable && metadataItem.deColumn))
60
+ ? this.createRESTBulk(metadataItem, '/messaging/v1/domainverification/') // bulk/insert
61
+ : super.createREST(metadataItem, '/messaging/v1/domainverification/');
62
+ }
63
+
64
+ /**
65
+ * Creates a multiple metadata entries via REST
66
+ *
67
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
68
+ * @param {string} uri rest endpoint for POST
69
+ * @returns {Promise.<object> | null} Promise of API response or null in case of an error
70
+ */
71
+ static async createRESTBulk(metadataEntry, uri) {
72
+ if (metadataEntry.deTable || metadataEntry.deColumn) {
73
+ Util.logger.error(
74
+ 'It seems the deTable/deColumn approach does not work. Supply emails via addresses-array instead'
75
+ );
76
+ return;
77
+ }
78
+ const createResults = {};
79
+ for (const address of metadataEntry.addresses) {
80
+ if (cache.getByKey(this.definition.type, address)) {
81
+ Util.logger.warn(` - ${this.definition.type} ${address} already exists`);
82
+ continue;
83
+ }
84
+ const item = { domain: address };
85
+ if (await this.createREST(item, uri)) {
86
+ createResults[address] = true;
87
+ }
88
+ }
89
+ if (Object.keys(createResults).length) {
90
+ // trigger re-retrieve to get all fields
91
+ await this.postDeployTasks(createResults);
92
+ await this.saveResults(
93
+ createResults,
94
+ [
95
+ this.properties.directories.retrieve,
96
+ this.buObject.credential,
97
+ this.buObject.businessUnit,
98
+ ].join('/'),
99
+ null
100
+ );
101
+ }
102
+ Util.logger.info(
103
+ `${this.definition.type} bulk load: ${Object.keys(createResults).length} of ${metadataEntry.addresses.length} created`
104
+ );
105
+ if (Object.keys(createResults).length) {
106
+ Util.logger.warn('Please ignore the next message about filtering 1 item');
107
+ return `bulk successfully added.`;
108
+ } else {
109
+ return null;
110
+ }
58
111
  }
59
112
 
60
113
  /**
@@ -121,9 +174,13 @@ class DomainVerification extends MetadataType {
121
174
  * manages post retrieve steps
122
175
  *
123
176
  * @param {DomainVerificationItem} metadataItem a single item
124
- * @returns {DomainVerificationItem} metadata
177
+ * @returns {DomainVerificationItem|void} metadata
125
178
  */
126
179
  static postRetrieveTasks(metadataItem) {
180
+ if (typeof metadataItem === 'string' && metadataItem === 'bulk successfully added.') {
181
+ // this is a hack for bulk-creation only
182
+ return;
183
+ }
127
184
  if (metadataItem.status !== 'Verified') {
128
185
  Util.logger.warn(
129
186
  Util.getMsgPrefix(this.definition, metadataItem) +
@@ -146,6 +203,9 @@ class DomainVerification extends MetadataType {
146
203
  */
147
204
  static async postDeployTasks(upsertResults) {
148
205
  // re-retrieve all upserted items to ensure we have all fields (createdDate and modifiedDate are otherwise not present)
206
+ if (!Object.keys(upsertResults).length) {
207
+ return;
208
+ }
149
209
  Util.logger.debug(
150
210
  `Caching all ${this.definition.type} post-deploy to ensure we have all fields`
151
211
  );
@@ -198,9 +198,10 @@ class MetadataType {
198
198
  *
199
199
  * @param {MetadataTypeItem} metadataEntry a single metadata Entry
200
200
  * @param {object} apiResponse varies depending on the API call
201
+ * @param {MetadataTypeItem} metadataEntryWithAllFields like metadataEntry but before non-creatable fields were stripped
201
202
  * @returns {Promise.<object>} apiResponse, potentially modified
202
203
  */
203
- static postUpdateTasks(metadataEntry, apiResponse) {
204
+ static postUpdateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {
204
205
  return apiResponse;
205
206
  }
206
207
 
@@ -1262,13 +1263,14 @@ class MetadataType {
1262
1263
  * @returns {Promise.<object> | null} Promise of API response or null in case of an error
1263
1264
  */
1264
1265
  static async updateREST(metadataEntry, uri, httpMethod = 'patch') {
1266
+ const metadataClone = structuredClone(metadataEntry);
1265
1267
  this.removeNotUpdateableFields(metadataEntry);
1266
1268
  try {
1267
1269
  // set to empty object in case API returned nothing to be able to update it in helper classes
1268
1270
  let response = (await this.client.rest[httpMethod](uri, metadataEntry)) || {};
1269
1271
  await this._postChangeKeyTasks(metadataEntry);
1270
1272
  this.getErrorsREST(response);
1271
- response = await this.postUpdateTasks(metadataEntry, response);
1273
+ response = await this.postUpdateTasks(metadataEntry, response, metadataClone);
1272
1274
  // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name
1273
1275
  Util.logger.info(` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}`);
1274
1276
  return response;
@@ -1327,6 +1329,7 @@ class MetadataType {
1327
1329
  */
1328
1330
  static async updateSOAP(metadataEntry, handleOutside) {
1329
1331
  const soapType = this.definition.soapType || this.definition.type;
1332
+ const metadataClone = structuredClone(metadataEntry);
1330
1333
  this.removeNotUpdateableFields(metadataEntry);
1331
1334
  try {
1332
1335
  let response = await this.client.soap.update(
@@ -1340,7 +1343,7 @@ class MetadataType {
1340
1343
  );
1341
1344
  }
1342
1345
  await this._postChangeKeyTasks(metadataEntry);
1343
- response = await this.postUpdateTasks(metadataEntry, response);
1346
+ response = await this.postUpdateTasks(metadataEntry, response, metadataClone);
1344
1347
 
1345
1348
  return response;
1346
1349
  } catch (ex) {
@@ -1515,6 +1518,7 @@ class MetadataType {
1515
1518
  : [];
1516
1519
  const results = {};
1517
1520
  for (const item of metadataArr) {
1521
+ this.createCustomKeyField(item);
1518
1522
  const key = item[this.definition.keyField];
1519
1523
  results[key] = item;
1520
1524
  }
@@ -1617,6 +1621,13 @@ class MetadataType {
1617
1621
  }
1618
1622
  }
1619
1623
 
1624
+ /**
1625
+ * helper for {@link parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
1626
+ *
1627
+ * @param {MetadataTypeItem} metadata single item
1628
+ */
1629
+ static createCustomKeyField(metadata) {}
1630
+
1620
1631
  /**
1621
1632
  * Builds map of metadata entries mapped to their keyfields
1622
1633
  *
@@ -1632,18 +1643,21 @@ class MetadataType {
1632
1643
  if (Array.isArray(body)) {
1633
1644
  // in some cases data is just an array
1634
1645
  for (const item of body) {
1646
+ this.createCustomKeyField(item);
1635
1647
  const key = item[keyField];
1636
1648
  metadataStructure[key] = item;
1637
1649
  }
1638
1650
  } else if (body[bodyIteratorField]) {
1639
1651
  for (const item of body[bodyIteratorField]) {
1652
+ this.createCustomKeyField(item);
1640
1653
  const key = item[keyField];
1641
1654
  metadataStructure[key] = item;
1642
1655
  }
1643
1656
  } else if (singleRetrieve) {
1644
1657
  // some types will return a single item intead of an array if the key is supported by their api
1658
+ this.createCustomKeyField(body);
1645
1659
  // ! currently, the id: prefix is only supported by journey (interaction)
1646
- if ('string' === typeof singleRetrieve && singleRetrieve.startsWith('id:')) {
1660
+ if (singleRetrieve.startsWith('id:')) {
1647
1661
  singleRetrieve = body[keyField];
1648
1662
  }
1649
1663
  metadataStructure[singleRetrieve] = body;
@@ -60,61 +60,11 @@ class MobileKeyword extends MetadataType {
60
60
  }
61
61
 
62
62
  /**
63
- * Builds map of metadata entries mapped to their keyfields
64
- *
65
- * @param {object} body json of response body
66
- * @param {string} [singleRetrieve] key of single item to filter by
67
- * @returns {MetadataTypeMap} keyField => metadata map
68
- */
69
- static parseResponseBody(body, singleRetrieve) {
70
- const bodyIteratorField = this.definition.bodyIteratorField;
71
- const keyField = this.definition.keyField;
72
- const metadataStructure = {};
73
- if (body !== null) {
74
- if (Array.isArray(body)) {
75
- // in some cases data is just an array
76
- for (const item of body) {
77
- this.#createCustomKeyField(item);
78
- const key = item[keyField];
79
- metadataStructure[key] = item;
80
- }
81
- } else if (body[bodyIteratorField]) {
82
- for (const item of body[bodyIteratorField]) {
83
- this.#createCustomKeyField(item);
84
- const key = item[keyField];
85
- metadataStructure[key] = item;
86
- }
87
- } else if (singleRetrieve) {
88
- // some types will return a single item intead of an array if the key is supported by their api
89
- this.#createCustomKeyField(body);
90
- // ! currently, the id: prefix is only supported by journey (interaction)
91
- if (singleRetrieve.startsWith('id:')) {
92
- singleRetrieve = body[keyField];
93
- }
94
- metadataStructure[singleRetrieve] = body;
95
- return metadataStructure;
96
- }
97
- if (
98
- metadataStructure[singleRetrieve] &&
99
- (typeof singleRetrieve === 'string' || typeof singleRetrieve === 'number')
100
- ) {
101
- // in case we really just wanted one entry but couldnt do so in the api call, filter it here
102
- const single = {};
103
- single[singleRetrieve] = metadataStructure[singleRetrieve];
104
- return single;
105
- } else if (singleRetrieve) {
106
- return {};
107
- }
108
- }
109
- return metadataStructure;
110
- }
111
-
112
- /**
113
- * helper for {@link MobileKeyword.parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
63
+ * helper for {@link parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
114
64
  *
115
65
  * @param {MetadataTypeItem} metadata single item
116
66
  */
117
- static #createCustomKeyField(metadata) {
67
+ static createCustomKeyField(metadata) {
118
68
  metadata.c__codeKeyword = metadata.code.code + '.' + metadata.keyword;
119
69
  }
120
70
 
@@ -29,6 +29,7 @@ import cache from '../util/cache.js';
29
29
  * @augments MetadataType
30
30
  */
31
31
  class Verification extends MetadataType {
32
+ static verificationIdKeyMap;
32
33
  /**
33
34
  * Retrieves Metadata of Data Verification Activity.
34
35
  *
@@ -39,51 +40,63 @@ class Verification extends MetadataType {
39
40
  * @returns {Promise.<MetadataTypeMapObj>} Promise of metadata
40
41
  */
41
42
  static async retrieve(retrieveDir, _, __, key) {
42
- let paramArr = [];
43
- if (key?.startsWith('id:')) {
44
- paramArr = [key.slice(3)];
45
- } else if (key) {
46
- paramArr = [key];
43
+ const paramArr = [];
44
+ let automationKey;
45
+ if (key) {
46
+ const regex = /^(.*?)__s\d{1,3}\.\d{1,3}$/;
47
+ const match = key.match(regex);
48
+ if (match) {
49
+ // automation key found
50
+ automationKey = match[1];
51
+ } else {
52
+ // invalid key, unset it
53
+ Util.logger.error(`Invalid key: ${key}`);
54
+ return;
55
+ }
47
56
  }
48
- if (!paramArr.length) {
49
- // there is no API endpoint to retrieve all dataVerification items, so we need to retrieve all automations and iterate over their activities
50
- Util.logger.info(` - Caching dependent Metadata: automation`);
51
- Automation.client = this.client;
52
- Automation.buObject = this.buObject;
53
- Automation.properties = this.properties;
54
- Automation._skipNotificationRetrieve = true;
55
- delete Automation._cachedMetadataMap;
56
- const automationsMapObj = await Automation.retrieve();
57
- delete Automation._skipNotificationRetrieve;
58
- if (automationsMapObj?.metadata && Object.keys(automationsMapObj?.metadata).length) {
59
- if (!key) {
60
- // if we are not retrieving a single item, cache the automations for later use during retrieval of automations
61
- Automation._cachedMetadataMap = automationsMapObj?.metadata;
62
- }
63
- // automations found, lets iterate over their activities to find the dataVerification items
64
- const dataVerificationIds = [];
65
- for (const automation of Object.values(automationsMapObj.metadata)) {
66
- if (automation.steps) {
67
- for (const step of automation.steps) {
68
- for (const activity of step.activities) {
69
- if (
70
- activity.objectTypeId === 1000 &&
71
- activity.activityObjectId &&
72
- activity.activityObjectId !==
73
- '00000000-0000-0000-0000-000000000000'
74
- ) {
75
- dataVerificationIds.push(activity.activityObjectId);
76
- }
57
+ const results = {};
58
+ // there is no API endpoint to retrieve all dataVerification items, so we need to retrieve all automations and iterate over their activities
59
+ Util.logger.info(` - Caching dependent Metadata: automation`);
60
+ Automation.client = this.client;
61
+ Automation.buObject = this.buObject;
62
+ Automation.properties = this.properties;
63
+ Automation._skipNotificationRetrieve = true;
64
+ delete Automation._cachedMetadataMap;
65
+ const automationsMapObj = automationKey
66
+ ? await Automation.retrieve(undefined, undefined, undefined, automationKey)
67
+ : await Automation.retrieve();
68
+ delete Automation._skipNotificationRetrieve;
69
+ if (automationsMapObj?.metadata && Object.keys(automationsMapObj?.metadata).length) {
70
+ if (!key) {
71
+ // if we are not retrieving a single item, cache the automations for later use during retrieval of automations
72
+ Automation._cachedMetadataMap = automationsMapObj?.metadata;
73
+ }
74
+ // automations found, lets iterate over their activities to find the dataVerification items
75
+ this.verificationIdKeyMap = {};
76
+ for (const automation of Object.values(automationsMapObj.metadata)) {
77
+ if (automation.steps) {
78
+ for (const step of automation.steps) {
79
+ // ideally one would use activity.displayOrder here but that doesnt always start at 1 nor is it always sequential. To avoid cross-BU issues, we use a custom order
80
+ let order = 1;
81
+ for (const activity of step.activities) {
82
+ if (
83
+ activity.objectTypeId === 1000 &&
84
+ activity.activityObjectId &&
85
+ activity.activityObjectId !== '00000000-0000-0000-0000-000000000000'
86
+ ) {
87
+ // log the verification id
88
+ this.verificationIdKeyMap[activity.activityObjectId] =
89
+ `${automation.key}__s${step.step}.${order}`;
77
90
  }
91
+ order++;
78
92
  }
79
93
  }
80
94
  }
81
- if (dataVerificationIds.length) {
82
- paramArr.push(...dataVerificationIds);
83
- }
95
+ }
96
+ if (Object.keys(this.verificationIdKeyMap).length) {
97
+ paramArr.push(...Object.keys(this.verificationIdKeyMap));
84
98
  }
85
99
  }
86
- const results = {};
87
100
  if (paramArr.length) {
88
101
  const response = await this.retrieveRESTcollection(
89
102
  paramArr.map((id) => ({ id, uri: '/automation/v1/dataverifications/' + id })),
@@ -131,7 +144,7 @@ class Verification extends MetadataType {
131
144
  }
132
145
 
133
146
  /**
134
- * Retrieves Metadata of Data Extract Activity for caching
147
+ * Retrieves Metadata of item for caching
135
148
  *
136
149
  * @returns {Promise.<MetadataTypeMapObj>} Promise of metadata
137
150
  */
@@ -140,9 +153,9 @@ class Verification extends MetadataType {
140
153
  }
141
154
 
142
155
  /**
143
- * Creates a single Data Extract
156
+ * Creates a single item
144
157
  *
145
- * @param {VerificationItem} metadata a single Data Extract
158
+ * @param {VerificationItem} metadata a single item
146
159
  * @returns {Promise} Promise
147
160
  */
148
161
  static create(metadata) {
@@ -161,31 +174,41 @@ class Verification extends MetadataType {
161
174
  if (!apiResponse?.[this.definition.idField]) {
162
175
  return;
163
176
  }
164
- Util.logger.warn(
165
- ` - ${this.definition.type} ${
166
- metadataEntryWithAllFields?.[this.definition.idField]
167
- }: new key ${
168
- apiResponse?.[this.definition.idField]
169
- } automatically assigned during creation`
170
- );
177
+ // update apiResponse to ensure the new metadata is saved correctly on disk
178
+ apiResponse[this.definition.keyField] =
179
+ metadataEntryWithAllFields?.[this.definition.keyField];
180
+
181
+ // update info on metadataEntry to allow for proper logs
182
+ metadataEntry[this.definition.keyField] =
183
+ metadataEntryWithAllFields?.[this.definition.keyField];
171
184
  metadataEntry[this.definition.idField] = apiResponse?.[this.definition.idField];
172
185
 
173
- // map structure: cred/bu --> type --> old key --> new key
174
- const buName = this.buObject.credential + '/' + this.buObject.businessUnit;
175
- Automation.createdKeyMap ||= {};
176
- Automation.createdKeyMap[buName] ||= {};
177
- Automation.createdKeyMap[buName][this.definition.type] ||= {};
178
- Automation.createdKeyMap[buName][this.definition.type][
179
- metadataEntryWithAllFields[this.definition.idField]
180
- ] = metadataEntry[this.definition.idField];
186
+ return apiResponse;
187
+ }
188
+ /**
189
+ * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP}
190
+ *
191
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
192
+ * @param {object} apiResponse varies depending on the API call
193
+ * @param {MetadataTypeItem} metadataEntryWithAllFields like metadataEntry but before non-creatable fields were stripped
194
+ * @returns {Promise.<object>} apiResponse, potentially modified
195
+ */
196
+ static postUpdateTasks(metadataEntry, apiResponse, metadataEntryWithAllFields) {
197
+ // update apiResponse to ensure the new metadata is saved correctly on disk
198
+ apiResponse[this.definition.keyField] =
199
+ metadataEntryWithAllFields?.[this.definition.keyField];
181
200
 
201
+ // update info on metadataEntry to allow for proper logs
202
+ metadataEntry[this.definition.keyField] =
203
+ metadataEntryWithAllFields?.[this.definition.keyField];
204
+ metadataEntry[this.definition.idField] = apiResponse?.[this.definition.idField];
182
205
  return apiResponse;
183
206
  }
184
207
 
185
208
  /**
186
- * Updates a single Data Extract
209
+ * Updates a single item
187
210
  *
188
- * @param {VerificationItem} metadata a single Data Extract
211
+ * @param {VerificationItem} metadata a single item
189
212
  * @returns {Promise} Promise
190
213
  */
191
214
  static update(metadata) {
@@ -209,9 +232,22 @@ class Verification extends MetadataType {
209
232
  'ObjectID'
210
233
  );
211
234
  delete metadata.r__dataExtension_key;
235
+
212
236
  return metadata;
213
237
  }
214
238
 
239
+ /**
240
+ * helper for {@link parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
241
+ *
242
+ * @param {MetadataTypeItem} metadata single item
243
+ */
244
+ static createCustomKeyField(metadata) {
245
+ if (this.verificationIdKeyMap[metadata[this.definition.idField]]) {
246
+ metadata[this.definition.keyField] =
247
+ this.verificationIdKeyMap[metadata[this.definition.idField]];
248
+ }
249
+ }
250
+
215
251
  /**
216
252
  * parses retrieved Metadata before saving
217
253
  *
@@ -39,9 +39,9 @@ export default {
39
39
  maxKeyLength: 36, // confirmed max length
40
40
  type: 'triggeredSend',
41
41
  soapType: 'triggeredSendDefinition',
42
- typeDescription: 'DEPRECATED: Sends emails via API or DataExtension Event.',
43
- typeRetrieveByDefault: true,
44
- typeCdpByDefault: true,
42
+ typeDescription: 'Used by Journey Builder to send triggered emails',
43
+ typeRetrieveByDefault: false,
44
+ typeCdpByDefault: false,
45
45
  typeName: 'Triggered Send',
46
46
  priorityMapping: {
47
47
  High: 3,