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.
- package/.fork/custom-commands.json +12 -0
- package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
- package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +19 -0
- package/docs/dist/documentation.md +400 -9
- package/lib/MetadataTypeDefinitions.js +1 -0
- package/lib/MetadataTypeInfo.js +1 -0
- package/lib/cli.js +6 -1
- package/lib/index.js +4 -1
- package/lib/metadataTypes/AttributeSet.js +118 -11
- package/lib/metadataTypes/Automation.js +99 -70
- package/lib/metadataTypes/DataExtension.js +463 -66
- package/lib/metadataTypes/DataExtensionField.js +30 -13
- package/lib/metadataTypes/Journey.js +8 -1
- package/lib/metadataTypes/MetadataType.js +63 -5
- package/lib/metadataTypes/MobileKeyword.js +1 -1
- package/lib/metadataTypes/TransactionalEmail.js +94 -17
- package/lib/metadataTypes/TransactionalMessage.js +3 -2
- package/lib/metadataTypes/Verification.js +230 -0
- package/lib/metadataTypes/definitions/AttributeGroup.definition.js +2 -2
- package/lib/metadataTypes/definitions/AttributeSet.definition.js +74 -21
- package/lib/metadataTypes/definitions/Automation.definition.js +1 -0
- package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +19 -1
- package/lib/metadataTypes/definitions/Verification.definition.js +88 -0
- package/package.json +6 -6
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testExisting_dataExtensionShared.dataExtension-meta.json +59 -0
- package/test/mockRoot/deploy/testInstance/_ParentBU_/dataExtension/testNew_dataExtensionShared.dataExtension-meta.json +23 -0
- package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +4 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testExisting_dataExtension.dataExtension-meta.json +1 -0
- package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +3 -4
- package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +1 -6
- package/test/mockRoot/deploy/testInstance/testBU/verification/testExisting_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/verification/testNew_39f6a488-20eb-4ba0-b0b9.verification-meta.json +11 -0
- package/test/resources/1111111/data/v1/customobjectdata/key/testExisting_dataExtensionShared/rowset/get-response.json +13 -0
- package/test/resources/1111111/dataExtension/create-expected.json +23 -0
- package/test/resources/1111111/dataExtension/create-response.xml +59 -0
- package/test/resources/1111111/dataExtension/retrieve-expected.json +55 -0
- package/test/resources/1111111/dataExtension/retrieve-expected.md +18 -0
- package/test/resources/1111111/dataExtension/retrieve-response.xml +27 -1
- package/test/resources/1111111/dataExtension/update-expected.json +55 -0
- package/test/resources/1111111/dataExtension/update-response.xml +57 -0
- package/test/resources/1111111/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtensionShared].[TriggerUpdate_randomNumber_]-response.xml +45 -0
- package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
- package/test/resources/1111111/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionSharedORDataExtension.CustomerKey=testExisting_dataExtensionShared-response.xml +98 -0
- package/test/resources/1111111/dataExtensionField/retrieve-response.xml +98 -0
- package/test/resources/1111111/dataExtensionTemplate/retrieve-response.xml +303 -0
- package/test/resources/1111111/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +387 -0
- package/test/resources/1111111/dataFolder/retrieve-response.xml +353 -9
- package/test/resources/9999999/attributeSet/retrieve-expected.json +89 -694
- package/test/resources/9999999/automation/build-expected.json +4 -0
- package/test/resources/9999999/automation/create-expected.json +4 -0
- package/test/resources/9999999/automation/create-testNew_automation-expected.md +1 -0
- package/test/resources/9999999/automation/retrieve-expected.json +4 -0
- package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +1 -0
- package/test/resources/9999999/automation/template-expected.json +4 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +7 -0
- package/test/resources/9999999/automation/v1/automations/post-response.json +7 -0
- package/test/resources/9999999/automation/v1/dataverifications/post-response.json +12 -0
- package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/delete-response.json +0 -0
- package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/get-response.json +12 -0
- package/test/resources/9999999/automation/v1/dataverifications/testExisting_39f6a488-20eb-4ba0-b0b9/patch-response.json +12 -0
- package/test/resources/9999999/dataExtension/build-expected.json +16 -0
- package/test/resources/9999999/dataExtension/delete-response.xml +42 -0
- package/test/resources/9999999/dataExtension/retrieve-expected.json +16 -0
- package/test/resources/9999999/dataExtension/retrieve-expected.md +3 -1
- package/test/resources/9999999/dataExtension/template-expected.json +16 -0
- package/test/resources/9999999/dataExtension/update-expected.json +17 -1
- package/test/resources/9999999/dataExtensionField/retrieve-CustomerKey=[testExisting_dataExtension].[LastName]-response.xml +44 -0
- package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +36 -1
- package/test/resources/9999999/dataExtensionField/retrieve-response.xml +36 -1
- package/test/resources/9999999/dataFolder/retrieve-ContentType=synchronizeddataextensionORContentType=shared_salesforcedataextensionORContentType=shared_dataextensionORContentType=shared_dataORContentType=salesforcedataextensionORContentType=dataextensionORContentType=hidden-response.xml +117 -0
- package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +43 -0
- package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +387 -0
- package/test/resources/9999999/interaction/v1/interactions/233d4413-922c-4568-85a5-e5cc77efc3be/delete-response.json +1 -0
- package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +1 -1
- package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
- package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/delete-response.json +6 -0
- package/test/resources/9999999/transactionalEmail/build-expected.json +3 -7
- package/test/resources/9999999/transactionalEmail/get-expected.json +3 -7
- package/test/resources/9999999/transactionalEmail/patch-expected.json +3 -7
- package/test/resources/9999999/transactionalEmail/post-expected.json +3 -7
- package/test/resources/9999999/transactionalEmail/template-expected.json +3 -7
- package/test/resources/9999999/verification/build-expected.json +11 -0
- package/test/resources/9999999/verification/get-expected.json +11 -0
- package/test/resources/9999999/verification/patch-expected.json +11 -0
- package/test/resources/9999999/verification/post-expected.json +11 -0
- package/test/resources/9999999/verification/template-expected.json +11 -0
- package/test/type.attributeGroup.test.js +4 -4
- package/test/type.attributeSet.test.js +5 -5
- package/test/type.automation.test.js +29 -23
- package/test/type.dataExtension.test.js +205 -45
- package/test/type.dataExtract.test.js +10 -3
- package/test/type.fileTransfer.test.js +10 -3
- package/test/type.importFile.test.js +10 -3
- package/test/type.journey.test.js +38 -11
- package/test/type.mobileKeyword.test.js +6 -4
- package/test/type.mobileMessage.test.js +6 -4
- package/test/type.query.test.js +8 -6
- package/test/type.script.test.js +6 -1
- package/test/type.transactionalEmail.test.js +12 -11
- package/test/type.transactionalPush.test.js +2 -4
- package/test/type.transactionalSMS.test.js +2 -4
- package/test/type.triggeredSend.test.js +6 -4
- package/test/type.verification.test.js +173 -0
- package/test/utils.js +7 -1
- 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
|
-
|
|
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
|
|
272
|
-
return this.retrieve(null, additionalFields, subTypeArr
|
|
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.
|
|
43
|
+
if (metadata.r__asset_customerKey) {
|
|
44
44
|
// we merely want to be able to show an error if it does not exist
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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?.
|
|
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.
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
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: '
|
|
12
|
-
typeRetrieveByDefault:
|
|
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: {
|